!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: object3.gs,v 1.17 2008-01-09 22:50:12 stever Exp $
!
! Methods from Object with indexing support
!
! Superclass Hierarchy:
!   Object.
!
!=========================================================================

! do not remove methods

category: 'Indexing Support'
method: Object
_findIVOffsetForPathTerm: pathTerm

"Returns the instance variable offset stored in the dependency list that
 corresponds to the given path term.  Returns nil if the path term is not found
 in the dependency list."

| depList offset |
(depList := DependencyList for: self) == nil
    ifTrue: [ ^ nil ].

offset := depList getInstVarOffsetWithPathTerm: pathTerm.
offset == 0
  ifTrue: [ ^nil ].
^offset
%

category: 'Indexing Support'
method: Object
_modifyingByteObjectStartingAt: startPt withNewValue: newValue

"The byte object is being changed.  Update any indexes that depend
 upon the byte object."

| depList inIndex |

self class isBytes not
    ifTrue: [
        ^ nil
    ].

" if no dependencies, just exit "
depList := DependencyList for: self.
depList == nil
    ifTrue: [
        ^ nil
    ].

inIndex := false.
" first try any modification trackers "
1 to: depList size by: 2 do: [ :i |
  (depList at: i + 1) > 0
    ifTrue: [ inIndex := true ]
    ifFalse: [
      (depList at: i) modifyingByteObject: self
        startingAt: startPt
        withNewValue: newValue
    ]
].
inIndex
  ifFalse: [
    ^ nil
  ].
"We handle index objects by removing them bTrees"
^ self _removeObjectFromBtrees
%

category: 'Indexing Support'
method: Object
_modifyingInstVarAtOffset: anInteger
to: newValue

"The instance variable at the given offset is being modified.  Update any
 indexes that depend upon that instance variable.  Returns true if the index
 objects were modified correctly; otherwise returns an Array containing error
 information."

<primitive: 901>
| depList i result inIndex |

inIndex := false.
depList := DependencyList for: self.
" first handle modification tracking "
1 to: depList size by: 2 do: [ :i |
  (depList at: i + 1) > 0
    ifTrue: [ inIndex := true ]
    ifFalse: [
      anInteger >= self class _firstGciPublicInstVar
        ifTrue: [
          (depList at: i) modifyingObject: self atOffset: anInteger to: newValue
        ]
    ]
].

inIndex
  ifFalse: [
    System _disableProtectedMode.
    ^ true
  ].

[
	System _bypassReadAuth: true.
	Exception
	    category: nil
	    number: nil
	    do: [ :ex :cat :num :args | | txt |
	        " get the text for the raised error "
	        txt := cat textForError: num args: args.
	        " returns Array containing error information "
	        ^ #[ true " indicating indexing objects may have been modified ",
	            num,
	            txt ]
	    ].

	i := 1.
	" check current size in case dependency list has changed "
	[ i <= depList size ] whileTrue: [
	    (depList at: i + 1) == anInteger
	        ifTrue: [
	            result := (depList at: i) update: self
	               at: anInteger
	               to: newValue.
	            result == true
	                ifFalse: [
	                    ^ result
	                ]
	        ].
	    i := i + 2
	].
] ensure: [ 
	System _bypassReadAuth: false.
	System _disableProtectedMode.
].
^ true
%

category: 'Indexing Support'
method: Object
_addObjectToBtreesWithValues: anArray

"Adds the receiver to the B-trees of any equality indexes that it participates
 in, using the given Array of index object/value pairs."

<primitive: 901>
| indexObj vals |
anArray == nil
    ifTrue: [
	  System _disableProtectedMode.
        ^ self
    ].

[
	System _bypassReadAuth: true.
	1 to: anArray size by: 2 do: [ :i |
	    indexObj := anArray at: i.
	    vals := anArray at: i + 1.
	    1 to: vals size do: [ :j |
	        " add an entry to the B-tree "
	        indexObj btreeAt: self put: (vals at: j)
	    ]
	].
] ensure: [ 
	System _bypassReadAuth: false.
	System _disableProtectedMode.
].
%

category: 'Indexing Support'
method: Object
_removeObjectFromBtrees

"Removes the receiver from the B-trees of any equality indexes that it
 participates in.  Returns an Array of index object/value pairs."

<primitive: 901>
| depList vals pathTerm |
depList := DependencyList for: self.
depList == nil
    ifTrue: [
	  System _disableProtectedMode.
        ^ nil
    ].
[
	System _bypassReadAuth: true.
	vals := Array new.
	" iterate through each path term dependent upon this object "
	1 to: depList size by: 2 do: [ :i |
	  (depList at: i + 1) > 0
	    ifTrue: [
	      pathTerm := depList at: i.
	      " for each index utilizing this path term "
	      1 to: pathTerm size do: [ :j | | indexObj |
	          indexObj := pathTerm at: j.
	          " add the equality index to the answer "
	          vals add: indexObj.
	          " remove the B-tree entries and get the corresponding values "
	          vals add: (indexObj _removeBtreeEntriesForKey: self)
	      ]
	    ]
	].
] ensure: [ 
	System _bypassReadAuth: false.
	System _disableProtectedMode.
].
^ vals
%

category: 'Indexing Support'
method: Object
getDepListAndAddPathTerm: pathTerm withIVOffset: ivOffset logging: aBoolean

"Private."

| depList depListClass dl |
self isInvariant
  ifTrue: [ ^ self ].

depListClass := DependencyList .
dl := depListClass for: self.
dl == nil
  ifTrue: [
    depList := depListClass new: 2.
    depList _basicAt: 1 put: pathTerm.
    depList _basicAt: 2 put: ivOffset.
  ]
  ifFalse: [
    depList := dl copyAndAddPathTerm: pathTerm withIVOffset: ivOffset for: self.
  ].

depListClass
  set: (SharedDependencyLists at: depList logging: aBoolean)
  for: self.
%

category: 'Indexing Support'
method: Object
_addToReferences: anObject offset: offset occurrences: num into: refsToRcvr

"Add anObject and the offset into the Array of references (refsToRcvr)
 if it is not already present."

1 to: refsToRcvr size by: 2 do: [ :i |
  anObject == (refsToRcvr at: i)
    ifTrue: [
      (refsToRcvr at: i + 1) == offset
        ifTrue: [ ^ self ]
    ]
].
num timesRepeat: [
  refsToRcvr add: anObject.
  refsToRcvr add: offset
]
%

category: 'Indexing Support'
method: Object
_getIndexReferencesInto: refsToRcvr

"Place information about references to the receiver due to the receiver's 
 participation in an index into the given Array.  The Array consists of pairs:

 1) An object that references the receiver.  If the object is directly
    contained in an indexed NSC, then this object is the NSC.  If the object
    in the dependency list is a tracking object (for object modification
    tracking), then this object is the tracking object.
 2) The offset of the instance variable in that object that references the
    receiver.  If the object is directly contained in an indexed NSC, this
    number is zero.  If the object in the dependency list is a tracking
    object, this number is -1.

 This is used only in support of the 'become:' method."

| depList pathTerm vals indexObj obj inDict |

depList := DependencyList for: self.
depList == nil
  ifTrue: [ ^ refsToRcvr ].

1 to: depList size by: 2 do: [ :i |

  vals := nil.

  (depList at: i + 1) > 0
    ifTrue: [
      pathTerm := depList at: i.
      " check if it is the first path term in an index (which means the receiver
      is directly contained in an indexed NSC "
      pathTerm size > 0
        ifTrue: [
          indexObj := pathTerm at: 1.
          pathTerm offset == 1
            ifTrue: [
                self _addToReferences: indexObj nscRoot
                  offset: 0
                  occurrences: (indexObj nscRoot occurrencesOf: self)
                  into: refsToRcvr
            ]
            ifFalse: [
              "determine if mapping is contained in index dictionary or B-tree"
              pathTerm isRangeEqualityIndexLastPathTerm
                ifTrue: [
                  " is either the next to the last or last object along the path "
                  " if the last object does not need a dependency list, then
                    self must be the next to the last object along the path
                    (since only it could have a dependency list) "
                  pathTerm needsDepList
                    ifTrue: [ " no choice but to check if its in the dictionary "
                      vals := indexObj indexDictionary at: self
                        term: (indexObj at: pathTerm offset - 1)
                        otherwise: nil.
                      inDict := vals ~~ nil.
                    ]
                    ifFalse: [ inDict := true ]
                ]
                ifFalse: [ inDict := true ].

              inDict
                ifTrue: [
                  " get the previous path term in the index "
                  pathTerm := indexObj at: pathTerm offset - 1.
                  vals == nil
                    ifTrue: [
                      " get the objects that refer to the receiver "
                      vals := indexObj indexDictionary at: self
                        term: pathTerm
                        ifAbsent: [
                          indexObj indexDictionary _errorNoEntryForKey: self
                              term: pathTerm
                              value: nil
                        ]
                    ].
    
                  (BucketValueBag _hasInstance: vals)
                      ifTrue: [
                          1 to: vals size do: [ :j |
                              obj := vals _at: j.
                              self _addToReferences: obj
                                offset: (pathTerm _ivOffsetFor: obj)
                                occurrences: 1
                                into: refsToRcvr
                          ]
                      ]
                      ifFalse: [
                          self _addToReferences: vals
                            offset: (pathTerm _ivOffsetFor: vals)
                            occurrences: 1
                            into: refsToRcvr
                      ]
                ]
                ifFalse: [
                  pathTerm updateBtree ~~ nil
                    ifTrue: [
                        vals := Array new.
                        pathTerm updateBtree btreeRoot
                          _findAllValuesForIdenticalKey: self into: vals.
                        1 to: vals size do: [ :j |
                            obj := vals _at: j.
                            self _addToReferences: obj
                              offset: (pathTerm _ivOffsetFor: obj)
                              occurrences: 1
                              into: refsToRcvr
                        ]
                    ]
                ]
            ]
        ]
    ]
    ifFalse: [ " it is a tracking object "
      refsToRcvr add: (depList at: i).
      refsToRcvr add: -1.
    ]
].

^ refsToRcvr
%

category: 'Indexing Support'
method: Object
_indexParticipationInfo

"Returns an Array of pairs: the root NSC and the path String that describes
 the path traversed from the root to reach the receiver."

^ self _indexParticipationInfoInto: Array new.
%

category: 'Indexing Support'
method: Object
_removeIndexParticipation: refs for: oldObject

"Private."

| obj offset |
Exception
  category: nil
  number: nil
  do: [ :ex :cat :num :args | | txt |
    " get the text for the raised error "
    txt := cat textForError: num args: args.
    num == (ErrorSymbols at: #rtErrPreventingCommit)
      ifTrue: [ " remove this exception and resignal "
        ^ ex resignal: cat number: num args: args
      ]
      ifFalse: [ " append additional message to the end of text "
        txt _error: #rtErrPreventingCommit
      ]
  ].
1 to: refs size by: 2 do: [ :i |
  obj := refs at: i.
  offset := refs at: i + 1.
  offset == 0
    ifTrue: [ " remove self from the NSC with indexes "
      obj remove: self
    ]
    ifFalse: [
      offset > 0
        ifTrue: [ " it is not a tracking object "
          " set the the instance variable reference to nil "
          obj instVarAt: offset put: nil
        ]
        ifFalse: [ " it is a tracking object "
          " send tracking msg first so if error is raise, we haven't
          cleared mod tracking yet "
          obj invokingBecomeOn: self to: oldObject.
          self _clearModificationTrackingTo: obj.
        ]
    ]
].
%

category: 'Indexing Support'
method: Object
_restoreIndexParticipation: refs

"Private."

| obj offset |
Exception
  category: nil
  number: nil
  do: [ :ex :cat :num :args | | txt |
    " get the text for the raised error "
    txt := cat textForError: num args: args.
    num == (ErrorSymbols at: #rtErrPreventingCommit)
      ifTrue: [ " remove this exception and resignal "
        ^ ex resignal: cat number: num args: args
      ]
      ifFalse: [ " append additional message to the end of text "
        txt _error: #rtErrPreventingCommit
      ]
  ].
1 to: refs size by: 2 do: [ :i |
  obj := refs at: i.
  offset := refs at: i + 1.
  offset == 0
    ifTrue: [ " add self back to the NSC "
      obj add: self.
    ]
    ifFalse: [
      offset == -1
        ifTrue: [ " it is a tracking object "
          self _setModificationTrackingTo: obj.
        ]
        ifFalse: [ " add self back to the NSC "
          obj instVarAt: offset put: self
        ]
    ]
].
%

category: 'Indexing Support'
method: Object
_indexParticipationInfoInto: array

"Returns an Array of pairs: the root NSC and the path String that describes
 the path traversed from the root to reach the receiver."

| depList pathTerm indexObj pathString included |
depList := DependencyList for: self.
1 to: depList size by: 2 do: [ :i |
  (depList at: i + 1) > 0
    ifTrue: [  
      pathTerm := depList at: i.
      1 to: pathTerm size do: [ :j |
          indexObj := pathTerm at: j.
          pathString := indexObj _partialPathComponentsStringUpTo: pathTerm offset.
          included := false.
          1 to: array size by: 2 do: [ :k |
              ( (array at: k) == indexObj nscRoot _and:
              [ (array at: k + 1) = pathString ] )
                  ifTrue: [ included := true ]
          ].
          included
              ifFalse: [
                  array add: indexObj nscRoot.
                  array add: pathString
              ]
      ]
    ]
].
^ array
%


category: 'Indexing Support'
method: Object
_checkConstraint: aClass onInstVarAt: offset

"This method is used during instance migration to check if the receiver
 participates in an index and if aClass would violate any equality indexes
 on the instance variable at the given offset.  Returns an error if an instance
 of aClass cannot be stored into the B-tree."

| depList pathTerm |
aClass == Object
  ifTrue: [
    ^ self
  ].

depList := DependencyList for: self.
" if no dependency list, we're done "
depList == nil
  ifTrue: [
    ^ self
  ].

pathTerm := depList _findPathTermForOffset: offset.
"XXX - a depList may have more than one pathTerm for an object, what now Batman?"
" if no path term or it's not the last term in an equality index, we're done "
( pathTerm == nil _or: [ pathTerm isRangeEqualityIndexLastPathTerm not ] )
  ifTrue: [
    ^ self
  ].

(aClass isSubclassOf: pathTerm updateBtree lastElementClass)
  ifFalse: [
    self _error: #rtErrBadConstraintForMigration
      args: #[ offset, pathTerm updateBtree lastElementClass ]
  ].
%

category: 'Indexing Support'
method: Object
_hasDependencyList

"Returns true if the receiver participates in an index, and false otherwise."

| result |
result := (DependencyList for: self) ~~ nil.
^ result
%

category: 'Indexing Support'
method: Object
_changingSizeTo: newSize

"Notifies any modification tracking objects that the receiver (an indexable
 object) is having its size changed."

| depList |
depList := DependencyList for: self.
1 to: depList size by: 2 do: [ :i |
  (depList at: i + 1) == 0
    ifTrue: [ (depList at: i) changingSizeOfObject: self to: newSize ]
].
%

category: 'Indexing Support'
method: Object
_deletingAt: offset count: count

"Notifies any modification tracking objects that the receiver (an indexable
 object) is having portions deleted."

| depList |
depList := DependencyList for: self.
1 to: depList size by: 2 do: [ :i |
  (depList at: i + 1) == 0
    ifTrue: [ (depList at: i) deletingIn: self startingAt: offset count: count ]
].
%

category: 'Indexing Support'
method: Object
_inserting: indexableObj at: offset insertSize: aSize

"Notifies any modification tracking objects that the receiver (an indexable
 object) is having portions inserted."

| depList |
depList := DependencyList for: self.
1 to: depList size by: 2 do: [ :i |
  (depList at: i + 1) == 0
    ifTrue: [ (depList at: i) inserting: indexableObj into: self at: offset 
				insertSize: aSize 
      ].
].
%

