'From Squeak3.1alpha of 28 February 2001 [latest update: #4147] on 11 June 2001 at 4:47:26 pm'! "Change Set: ConvMthFix2-tk Date: 11 June 2001 Author: Ted Kaehler Further fix the conversion method prototype. If a conversion method exists in the class, add a comment to the end. The comment explains which inst vars are changing this time. Programmer must test to see if this particular conversion is happening, and write code to fill in new inst vars. He must move the new code up into the body of the method (before the ^ super...). If the class has no conversion method, create a prototype. Programmer must test for this conversion and store values. New class comment."! !SmartRefStream commentStamp: 'tk 6/11/2001 16:46' prior: 0! Ordinary ReferenceStreams assume that the names and order of instance variables is exactly the same when an object file is written and read. SmartRefStream allows object files to be read even after instance variables have changed or the entire class has been renamed. When an object file is written, no one knows how the classes will change in the future. Therefore, all conversion must be done when the file is read. The key is to store enough information in the file about the names of the instance variables of all outgoing classes. SmartRefStream works best with only one tree of objects per file. You can nextPut: more than once, but each object tree gets its own class structure description, which is big. Conversion of old objects is done by a method in each class called (convertToCurrentVersion: varDict refStream: smartRefStrm). At fileOut time, ChangeSet>>checkForConversionMethods creates a prototype of this method (if Preference #conversionMethodsAtFileOut is true). The programmer must edit this method to (1) test if the incoming object needs conversion, (2) put non-nil values into any new inst vars that need them, and (3) save the data of any inst vars that are being deleted. Determining which old version is represented by the incoming object can be done in several ways: noticing that a current inst var is nil when it should have data, noticing that there is an older inst var name in the variable dictionary (varDict), checking kinds of objects in one or more inst vars, or retrieving the classVersion of the incoming object from the ref stream. If a class is renamed, a method goes into SmartRefStream telling the new name. The conversion method of the new class must be prepared to accept instances of the old class also. If no inst var names have changed, the conversion method does nothing. An example: Suppose we change the representation of class Rectangle from ('origin' 'corner') to ('origin' 'extent'). Suppose lots of Rectangle instances are already out on files (in .pr project files, especially). The programmer changes the class definition, modifies all the methods, and filesOut. A series of dialogs appear, asking if instances Rectangle might be in an object file, if 'extent' needs to be non-nil (yes), and if the info in 'corner' needs to be preserved (yes). This method appears: Rectangle >> convertToCurrentVersion: varDict refStream: smartRefStrm "These variables are automatically stored into the new instance: #('origin'). Test for this particular conversion. Get values using expressions like (varDict at: 'foo')." "New variables: #('extent'). If a non-nil value is needed, please assign it." "These are going away #('corner'). Possibly store their info in some other variable?" "Move your code above the ^ super... Delete extra comments." ^ super convertToCurrentVersion: varDict refStream: smartRefStrm The programmer modifies it to be: Rectangle >> convertToCurrentVersion: varDict refStream: smartRefStrm (varDict includesKey: 'extent') ifFalse: ["old version!!" "Create the new extent, and preserve the info from the old corner" extent _ (varDict at: 'corner') - origin. ]. ^ super convertToCurrentVersion: varDict refStream: smartRefStrm This conversion method stays in the system and is ready to convert the old format of Rectangle whenever one is encountered in an object file. Note that the subclasses of Rectangle, (B3DViewport, CharacterBlock, and Quadrangle) do not need conversion methods. Their instances will be converted by the code in Rectangle. Files written by SmartRefStream are in standard fileout format. You can mix raw objects with code to be filed in. The file starts out in the normal fileOut format. Definitions of new classes on the front. structures Dictionary of (#Rectangle -> #( 'origin' 'corner')). Inst var names are strings. steady Set of Classes who have the same structure now as on the incoming file. Includes classes with same inst vars except for new ones added on the end. reshaped Dictionary of Classes who have a different structure now from the incoming file. Includes those with same inst vars but new version number. (old class name -> method selector to fill in data for version to version) renamed Dictionary of Classes who have a different name. Make an instance of the new class, and send it the conversion call. (old class name symbol -> new class name). renamedConv Dictionary of conversion selector for Classes who have a different name. (old class name symbol -> conversion selector). topCall Tells if next or nextPut: are working on the top object in the tree. nil if outside, the top object if deep inside. See DataStream.typeIDFor: for where the tangle of objects is clipped, so the whole system will not be written on the file. No object that is written on the file is ever a class. All class definitions are filed in. A class may be stored inside an ImageSegment that itself is stored in a SmartRefStream. UniClasses are classes for the instance specific behavior of just one instance. Subclasses of Player are an example. When a UniClass is read in, and a class of the same name already exists, the incoming one is renamed. ObjectScanner converts the filed-in code. Values in instance variables of UniClasses are stored in the array that tells the class structure. It is the fourth of the four top level objects. #(version (class-structure) the-object ((#Player25 scripts slotInfo costumeDictionary) (#Player26 scripts slotInfo costumeDictionary))). There is a separate subclass for doing veryDeepCopy (in memory). Currently, any object for which objectToStoreOnDataStream return an object other than self, does this: The new object (a DiskProxy) is traced. When it comes time to go through the fields of the old object, they are not found as keys in references (DiskProxies are there instead). So the old field value is left in the new object. That is OK for StrikeFont, Class, MetaClass, DisplayScreen. But the DiskProxies are evaluated, which takes a lot of time. Some metaclasses are put into the structures table. This is for when a block has a receiver that is a class. See checkFatalReshape:. ImageSegments: A ReferenceStream is used to enumerate objects to put inside an ImageSegment. If an instance of a UniClass is seen, the class is put in also. A SmartRefStream is used to store the ImageSegment. Roots are nil, and the segment is a wordArray. We are encoding the outPointers. Structures contains all classes from both places. Must filter out UniClasses for some things, and do include them for putting source code at end of file. Do not write any class inst vars in file. --Ted Kaehler and Bob Arning. ! !SmartRefStream methodsFor: 'class changed shape' stamp: 'tk 6/11/2001 15:53'! writeConversionMethodIn: newClass fromInstVars: oldList to: newList renamedFrom: oldName "The method convertToCurrentVersion:refStream: was not found in newClass. Write a default conversion method for the author to modify. If method exists, append new info into the end." | code newOthers oldOthers copied newCode | newOthers _ newList asOrderedCollection "copy". oldOthers _ oldList asOrderedCollection "copy". copied _ OrderedCollection new. newList do: [:instVar | (oldList includes: instVar) ifTrue: [ instVar isInteger ifFalse: [copied add: instVar]. newOthers remove: instVar. oldOthers remove: instVar]]. code _ WriteStream on: (String new: 500). code cr; cr; tab; nextPutAll: '"From ', Smalltalk version, ' [', Smalltalk lastUpdateString; nextPutAll: '] on ', Date today printString, '"'; cr. code tab; nextPutAll: '"These variables are automatically stored into the new instance: '. code nextPutAll: copied asArray printString; nextPut: $.; cr. code tab; nextPutAll: 'Test for this particular conversion.'; nextPutAll: ' Get values using expressions like (varDict at: ''foo'')."'; cr; cr. (newOthers size = 0) & (oldOthers size = 0) & (oldName == nil) ifTrue: [^ self]. "Instance variables are the same. Only the order changed. No conversion needed." (newOthers size > 0) ifTrue: [ code tab; nextPutAll: '"New variables: ', newOthers asArray printString, '. If a non-nil value is needed, please assign it."'; cr]. (oldOthers size > 0) ifTrue: [ code tab; nextPutAll: '"These are going away ', oldOthers asArray printString, '. Possibly store their info in some other variable?"'; cr]. oldName ifNotNil: [ code tab; nextPutAll: '"Test for instances of class ', oldName, '.'; cr. code tab; nextPutAll: 'Instance vars with the same name have been moved here."'; cr. ]. code tab; nextPutAll: '"Move your code above the ^ super... Delete extra comments."'; cr. (newClass includesSelector: #convertToCurrentVersion:refStream:) ifTrue: ["append to old methods" newCode _ (newClass sourceCodeAt: #convertToCurrentVersion:refStream:), code contents] ifFalse: ["new method" newCode _ 'convertToCurrentVersion: varDict refStream: smartRefStrm', code contents, ' ^ super convertToCurrentVersion: varDict refStream: smartRefStrm']. newClass compile: newCode classified: 'object fileIn'. "If you write a conversion method beware that the class may need a version number change. This only happens when two conversion methods in the same class have the same selector name. (A) The inst var lists of the new and old versions intials as some older set of new and old inst var lists. or (B) Twice in a row, the class needs a conversion method, but the inst vars stay the same the whole time. (For an internal format change.) If either is the case, fileouts already written with the old (wrong) version number, say 2. Your method must be able to read files that say version 2 but are really 3, until you expunge the erroneous version 2 files from the universe." ! ! SmartRefStream removeSelector: #aComment!