Apparatus and method for allowing object-oriented programs created with different framework versions to communicate6272521Abstract A set of stream writer and reader classes and methods enable object frameworks to communicate with each other despite problems with missing classes due to mismatched versions. The stream writers are modified to deal with a new version of a class that extends from a class in an existing version by writing alternate object information compatible with the existing version when the future object class information is streamed. In this manner, alternate object information is written for each older version. The information for each of the alternate objects corresponding to each older version is added after the existing object information as an extension with the length of the extension written at the beginning. The stream readers are modified so that when an older version stream reader reads the object information and does not understand the first alternate object (which might correspond to a later version), it skips the length specified for that extension and reads the second alternate object. If the second alternate object information is not understood, the reader skips the non-understood object information and continues with each alternate object. If none of the alternates is understood, then an exception is thrown. In one embodiment, the information for alternate objects which are not used is not discarded, but is instead saved in a temporary storage. Then, if the object is streamed out again, the stored information is added back into the stream. Claims What is claimed is: Description COPYRIGHT NOTIFICATION
long x = 5; // sample data with known, primitive type
TFileStream f; // sample data stream
x >> = f; // executes f.Write(x)
In the Commonpoint system, the writers are designed to write out a "version number" associated with each object instance. The version numbers are updated whenever the external representation of the instance changes. The Read( ) function is the main Commonpoint function for reading information in from a data stream. It is indicated by the stream-in symbol "<<=" in the code fragments illustrated below. The Read( ) function must read in data in the exact same order as the Write( ) function which wrote it out. For example, if objects a, b , and c have been written to a stream, then the reader must read the stream back in order into matching variables for a, b, and c, when reading the streamed objects back into a program's memory space. When the "<<=" function is used, it really calls the Read( ) function of the data stream. Using the same stream (TFileStream f) as used in the Write( ) example above, the following code shows how to read from a stream into a variable:
long y; // create variable to read into
y << = f; // do f.Read(y)
Where pointers and more complicated objects are involved the global Flatten( ) function must be used. Flatten( ) is more intelligent than the write( ) function because it follows every pointer and nested instance contained in the original instance being streamed. In addition, the Flatten( ) function maintains the correct instance structure when object instances are resurrected. If only pointers to instance are written to a data stream, then, during resurrection, the pointers will end up pointing to unknown data. This is considered a run-time error, because accessing the pointers will generate unknown results. To guarantee the resurrected information is exactly the same as the information that was flattened, all information about the instance is written to the stream. In addition, the Flatten( ) function provides a default "context", which recognizes when more than one of the same object instance is present in the data to be streamed. By default, only the first instance is streamed out, and identification tokens are written for each similar instance streamed out after the first. Writing only the tokens achieves a reduction in the amount of data streamed, thereby creating efficiency through data compression. The code fragment below illustrates the use of the Flatten( ) function to flatten an object a: TPersistentClassA* a=new TPersistentClassA(`a`, 5); TFileStream aStream(new char[100], 100); Flatten(a,aStream); A TContext argument is used in the case where multiple references to the same object are flattened and packed together on the same stream. The TContext element is a dynamic dictionary built during the flattening process to assign references to repeated object instances from a set of objects to be saved. The code sample below shows multiple instance flattening techniques for flattening object instances d and a: TPersistentClassA* a=new TPersistentClassA(`a`, 5); TPersistentClassD* d=new TPersistentClassD("me", "cguy", "bguy", a); TFileStream aStream(new char[100], 100); TContext aContext; if (!aStream-.fwdarw.GetContext( )) aStream-.fwdarw.SetContext(&aContext); aStream.FlattenPointer(d); aStream.FlaftenPointer(a); if (aStream-.fwdarw.GetContext( )==&aContext) aStream-.fwdarw.SetContext(NIL); The Resurrect( ) function takes the flattened form of an instance and creates an object instance from it. The Resurrect( ) function takes as parameters a stream identification and a context identification. The stream which is passed in to the Resurrect( ) function as a parameter contains the flattened form of the instance. The context which is passed in is used in the same manner as described with reference to the Flatten( ) function. The following code fragment illustrates the use of Resurrect( ) to reconstruct a single instance of an object A from a data stream, aStream: TPersistentClassA* A; TFileStream aStream; A=(TPersistentClassA*) aStream.Resurrect( ); The following code fragment illustrates resurrecting two object instances of objects D and A from the same stream: TPersistentClassD* D; TPersistentClassA* A; TFileStream aStream; TContext aContext; if (!aStream-.fwdarw.GetContext( )) astream-.fwdarw.SetContext(&aContext); D=(TPersistentClassD*) aStream.Resurrect( ); A=(TPersistentClassA*) astream.Resurrect( ); if (aStream-.fwdarw.GetContext==&aContext) aStream-.fwdarw.SetContext(NIL); The following fragment is a definition of a custom stream-out operator (>>=) which flattens the base classes of its instances by explicitly calling the Flatten routine of the base classes. It then flattens the member instances by explicitly calling the Flatten routine of the member instances. Finally, it flattens its member which are primitive data types and references to other instances. Simple data types are flattened by writing the data type to the stream using the stream write( ) member function. References to other instances are flattened by recursively calling the FlattenPointer( ) routine with the parameters that were passed in. In this example, it is assumed that class C is descended from classes A and B, has a member instance d of class D, has a member with a primitive type long, a member with a primitive type char, and a pointer to an E instance (e). The WriteVersion( ) function writes the object version number to the stream as described above. The function, and modifications made in accordance with the invention, are discussed in detail below. The illustrative code fragment is as follows:
TStream& C::operator>>=(TStream* toWhere)
{
WriteVersion(toWhere);
A::operator>>=(toWhere);
B::operator>>=(toWhere);
d.operator>>=(toWhere);
fLong >>= toWhere;
fChar >>= toWhere;
toWhere.FlattenPointer(e);
return toWhere;
}
A customized stream-in operator (<<=) for resurrecting the object flattened by the above procedure is illustrated below. The ReadVersion( ) function is the counterpart of the WriteVersion( ) function which reads the version number. It is also discussed in detail below.
TStream& C::operator<<=(TStream* fromWhere)
{
VersionInfo v = ReadVersion (fromWhere);
if (v == kNewVersion)
{
A::operator<<=(fromWhere);
B::operator<<=(fromWhere);
d.operator<<=(fromWhere);
fLong <<= fromWhere;
fChar <<= fromWhere;
e = (E*) fromWhere.Resurrect();
}
else
{
//read the old version
...
}
return fromWhere;
}
Overview The overall issue of data compatibility between application programs constructed with different framework versions encompasses several sub issues. For example, data incompatibility can arise from mismatched class versions and from missing classes. There are also issues concerning data loss when incompatibilities are processed. Finally, there are issues concerning the reading of data streams by applications which were constructed without using any of the underlying framework versions. 1. Mismatched Class Versions A mismatched class version problem arises when a stream reader finds an unknown version of a class on a stream. In particular, an unknown version generally appears as unknown extensions to a known class. In accordance with the principles of the invention, this problem is solved by allowing the stream reader to skip and, possibly discard, unknown extension data. In particular, the stream writers are modified, as set forth in detail below, to control the format in which class extensions are written. In particular, the format for the original base class data is to write the version number, stream the base class data and then stream the derived class data as in the following example of a stream-out operator:
TStream& TClass::operator>>=(TStream& toWhere) {
::WriteVersion(kInitialVersion, toWhere);
TSuperClass::operator>>=(toWhere);
field00>>=toWhere;
field01>>=toWhere;
return toWhere;
};
The format for the stream-in operator is as follows:
TStream& TClass::operator<<=(TStream& fromWhere) {
::ReadVersion(kInitialVersion, kInitialVersion, fromWhere);
TSuperClass::operator<<=(fromWhere);
field00<<=fromWhere;
field01<<=fromWhere;
return fromWhere;
};
In addition, any future version of a class must add its extension fields in a group. The stream writer is modified to precede this group by the length of the group. This results in writing and reading the class information in the sequence illustrated in FIG. 3. FIG. 3 shows a class with N extensions written and read in the order indicated by arrow 316. When the class information is written to the stream, the version number 300 is written first, followed by the groups of extension fields. Each field group is preceded by the length of the group. For example, extension field group 304 is preceded by its group length 302. Group 308 is preceded by length 306 and group 312 is preceded by length 310, etc. The original class fields 314 are written last. The non-italicized fields indicate data that is specified or written to the stream by the object itself whereas the italicized fields indicate data that is written to the stream by streaming support code constructed in accordance with the principles of the invention. Note that the "newest" extensions are written first followed by "older" extensions. An illustrative stream-out operator for a class with two version extensions is as follows:
TStream& TClass::operator>>=(TStream& toWhere) {
::WriteVersion(kLatestVersion, 2, toWhere);
//Write data for version 2.
{ TExtensionOutputStream writeStream(toWhere, true);
field20>>=writeStream;
field21>>=writeStream;
};
//Write data for version 1
{ TExtensionOutputStream writeStream (toWhere, true);
field10>>=writeStream;
field11>>=writeStream;
};
//Initial data.
TSuperClass::operator>>=(toWhere);
field00>>=toWhere;
field01>>=toWhere;
return toWhere;
};
The corresponding stream-in operator is as follows:
TStream& TClass::operator<<=(TStream& fromWhere) {
VersionInfo version = ::ReadVersion(kInitialVersion, kLatestVersion,
fromWhere);
if (version >=2){
//Read data for version 2.
TExtensionInputStream readStream(fromWhere);
field20 <<=readStream;
field21 <<=readStream
}else {
//Default the version 2 data.
field20=fallbackValue20;
field21=fallbackValue21;
}
if(version >=1) {
//Read data for version 1.
TExtensionInputStream readStream(fromWhere);
field10<<=readStream;
field11<<=readStream;
}else {
//Default the version 1 data.
field10=fallbackValue10;
field11=fallbackValue11;
}
//Initial data.
TSuperClass::operator<<=(fromWhere);
field00<<=fromWhere;
field01<<=fromWhere;
return fromWhere;
};
When reading the stream, an earlier framework version reader reads the version number and then uses the lengths to skip extension fields until it gets to an extension it understands. It can either discard the skipped extensions or save them in a temporary storage called a "bit bucket" for use when streaming out the class as described below. One drawback of this approach is that the stream writer cannot predict the length of an extension group before writing each extension. On a random-access stream, this means that the system has to back-up, write the length, then reset. This may involve flushing file buffers multiple times if the data crosses buffer boundaries. On a non-random access stream, this means that the system has to write the extension data to an in-memory stream and then write the length and copy the in-memory stream to the main stream. This may have a significant performance and memory impact. To mitigate this impact, the compatibility support described below only wraps extensions only if it is absolutely necessary. If the stream is not persistent then the extension-wrapping code does nothing. 2. Missing Classes A missing class data compatibility problem occurs when a stream reader finds an unknown class on the stream. In some cases, stream writers do not indicate the ends of streamed objects, as is the case with the aforementioned Commonpoint system. Therefore, the stream reader is unable to skip the unknown object and any stream which contains an unknown class is unreadable from that class onward. This can be a problem for both forward and backward data compatibility. Obviously, there may be unknown classes on streams from newer framework versions because the newer framework versions contain new classes. But new framework versions may not recognize some of the classes on streams from old framework versions if some of the old classes have been deleted. To avoid the latter problem, persistent classes are not deleted from the old framework versions. There is still a problem with new classes, but fortunately, not all new classes have the potential to cause a problem. Problems occur for new classes that descend from existing classes, and only when those new classes are streamed polymorphically. For example, assume that an old framework version has a text style called TTextStyle and a new framework version introduces a new text style: TWavyUnderlineStyle, which descends from TTextStyle. In the new framework, the TWavyUnderlineStyle could be streamed monomorphically either by a new class or it could be streamed monomorphically by a new extension of an old class. However, it could not be streamed monomorphically by the base version of the old class since the TWavyUnderlineStyle does not exist in the old framework version. Since the TWavyUnderlineStyle cannot be streamed by the base version of an old class, and, since that is the only version that is read by the old framework streaming functions, no old framework will ever see a monomorphic TWavyUnderlineStyle. So the missing class problem can never happen for monomorphically streamed classes. However, an old framework reader might encounter a TWavyUnderlineStyle that was streamed polymorphically. For example, assume that there is an old class called TStyleBundle that streams text styles polymorphically. In the new framework, the TStyleBundle class might contain a TWavyUnderlineStyle. If a TStyleBundle containing a TWavyUnderlineStyle is streamed by an application program built with the new framework, and that stream is read by an application built with the old framework, the old TStyleBundle class stream-in operator will attempt to resurrect an old TTextStyle. Since the old TTextStyle has been replaced by a TWavyUnderlineStyle, the resurrection will fail. In accordance with the principles of the present invention, the stream writers are modified so that they write an "alternate" old class when every new class that descends from an existing class is streamed. Each of the alternates, including the original class, is wrapped like an extension with the length at the beginning. This results in writing and reading the class information in the sequence illustrated in FIG. 4. FIG. 4 shows a class with an alternate written and read in the order indicated by arrow 420. When the class information is written to the stream, the start alternate name 400 is written first. The start alternate is the original class information. The information for each alternate is preceded by the length of the alternate. For example, the length 402 of the start alternate 400 precedes the information for that alternate. Type information 404 is written next and the alternate version number 406 is written next, followed by a group 408 of alternate fields. In a similar manner, the next alternate 410 comprises a name 410 followed by length 412, type info 414 and a group of fields 418. The non-italicized fields indicate data that is specified or written to the stream by the object itself whereas the italicized fields indicate data that is written to the stream by streaming support code constructed in accordance with the principles of the invention. As with the extensions, the "newest" alternates are written first followed by "older" alternates. Then, stream readers that don't understand the first alternate can use the length to skip it. They can then read subsequent alternates until they reach one that they understand. Skipped alternates may be discarded or may be saved in a bit bucket. If the stream reader does not understand any of the alternates, the inventive Resurrect( ) function will skip all of the alternates and throw an exception. Application programs that can catch this exception may then proceed to read the rest of the stream. Illustrative code fragments showing stream-in and stream-out operators for classes with alternates are as follows:
TStream& TClass::operator>>=(TStream& toWhere) {
TAlternateOutputStream altStream(toWhere);
//Write the stream.
::WriteVersion(kLatestVersion, 2, altStream);
{ TExtensionOutputStream writeStream(altStream, true);
field 20>>=writeStream;
field 21>>=writeStream;
};
{ TExtensionOutputStream writeStream(altStream, true);
field10>>=writeStream;
field11>>=writeStream;
};
TSuperClass::operator>>=(altStream);
field00>>=altStream;
field01>>=altStream;
//Write the alternate
if (altStream PrepareForNextAlternate()) {
//Intialize and flatten the 1.0 alternate.
TSomeRelated 1.0Class altClass (...);
::Flatten(&altClass, altStream);
}
return to Where;
};
TStream&TClass::operator<<=(TStream&fromWhere) {
TAlternateInputStream altStream(fromWhere);
VersionInfo version = ::ReadVersion(kInitialVersion, kLatestVersion,
altStream);
if (version >=2 {
//Read data for version 2.
TExtensionInputStream readStream(altStream);
field20<<=readStream;
field21<<=readStream;
} else {
// Default the version 2 data.
field20=fallbackValue20;
field21=fallbackValue 21;
}
if (version >=1){
//Read data for version 1.
TExtensionInputStream readStream(altStream);
field10<<=readStream;
field11<<=readStream;
} else {
//Default the version 1 data.
field10=fallbackValue10;
field11=fallbackValue11;
}
//Initial data.
TSuperClass::operator<<=(altStream);
field00<<=altStream;
field01<<=altStream;
return fromWhere;
};
One drawback to this solution is that the alternates will slow down the streaming operators and increase the size of the stream. Writing the stream takes longer because the alternate class has to be constructed, initialized and streamed, and the stream is larger because it contains the alternate data. To mitigate this cost, the compatibility support code disclosed below only writes alternate data if it is absolutely required. If the object is not persistent, or if the object is not being streamed polymorphically, then no alternate data will be written. The form of the streaming operators which use alternates is as follows:
TStream& TNewClass::operator>>=(TStream& toWhere)
{
TAlternateOutputStream altStream(toWhere);
//Stream myself--follow the recommended format.
WriteVersion(KinitialVersion, altStream);
TSuperClass::operator>>=(altStream);
fData1>>=altStream;
...
//PrepareForNextAlternate will return TRUE only if an alternate
is required.
if (altStream.PrepareForNextAlternate()) {
//Initialize and flatten the old framework alternate class.
TSomeRelatedOldClass altClass(...);
::Flatten(&altClass, altStream);
}
return toWhere;
}
TStream& TNewClass::operator<<=(TStream& fromWhere)
{
TAlternateInputStream altStream(fromWhere);
//Stream myself--follow the recommended format.
ReadVersion(kinitialVersion, kinitialVersion, altStream);
TSuperClass::operator<<=(altStream);
fData1 <<=altStream;
...
return fromWhere;
}
Under some circumstances it may be desirable for a particular application program to supplement or override the alternates for a class. In this case a function, FlattenWithoutAlternates( ) is used to do this. FlattenWithoutAlternates( ) puts the given object on the stream without any of its alternates. The following code fragment illustrates how an application can use the FlattenWithoutAlternates( ) to override an object's default alternates and supply a special alternate stream. This example assumes the application wants to stream a type of object called an MGraphic and wants to provide its own alternate object.
for each MGraphic* g{
//Create special alternate stream
TAlternateOutputStream altStream(toWhere);
//Stream the MGraphic--but not any of its alternates.
FlattenWithoutAlternates(g, altStream);
if (altStream.PrepareForNextAlternate()) {
//Now flatten the special alternate.
Flatten(myAlternateObject, altStream);
}
}
3. Avoiding Data Loss The aforementioned data compatibility support allows stream readers to skip streamed extensions and alternates that they do not understand. This can result in data loss when streams are exchanged between sites. For example, suppose user 1 and user 2 are collaborating on a document that contains an extended graphic object. The object is a polygon, but in a new framework, it has been extended to include a curve. User 1 is using an application program built with the new framework, but user 2 is using a program built with an old framework version. User 1 edits the object, introducing curve edits and streams the object to user 2. User 2's old stream reader discards the unknown curve data and presents the polygon to user 2. User 2 edits the polygon, and streams the polygon back to user 1, but user 1's curve edits have been lost. To avoid this kind of data loss, the inventive system provides "bit bucket" classes that can be used to capture and preserve unread stream data. To preserve unread extension data the inventive system provides a TExtensionBitBucket class. In use, a TExtensionBitBucket object is passed to the ReadVersion( ) function, described above, which will deposit skipped extensions into it rather than discarding them. The information is then temporarily saved. A TExtensionBitBucket object can also be passed to the WriteVersion( ) function, which will write the saved contents to the stream. However, the application program must decide whether or not to write the saved information to the stream. If the base object has not changed, then clearly it is safe to write out the saved information. But if the base object has changed, then the saved information might be inconsistent with the new object. To help the client to decide whether or not to write the saved information, a TExtensionBitBucket object maintains an "orthogonal" flag. This flag indicates whether the saved data contained in the object is affected by, or independent of (orthogonal), to the base data. If the flag is TRUE, then the saved data will be consistent with the base object no matter how the base object has changed. The BitBucket objects can be used in the example above. If the developer of the new framework suspects that the polygon class may be extended in the future, a TExtensionBitBucket class can be included in the places where the polygon class is streamed. The resulting BitBucket object will catch future extensions of the class. When the polygon object is extended to include the curve, the polygon's streaming operators are also extended to read and write the curve. In the stream-out operator for the polygon class, the developer sets the flag that indicates that the curve data is orthogonal to the polygon data. So now, when user 1 streams the polygon to user 2, the extra curve data is not discarded. Instead, the revised code saves it in a bit bucket. User 2 edits the polygon, and then sends it back to user 1. When user 2 streams the polygon data, the stream writer also streams the saved data because the orthogonal flag is set to TRUE. User 1 then receives the edited curve data back, plus the polygon data as edited by user 2 and the work of both users is preserved. To preserve unread alternate class data, the inventive system provides a TAlternateBitBucket class. During use, the application program can pass a TAlternateBitBucket object to the Resurrect( ) function, which will dump all of the unread alternates into it. An application program can also pass a TAlternateBitBucket object to the Flatten( ) function, which will write out the alternates with the flattened class. The TAlternateBitBucket class is used in the same way as the TExtensionBitBucket class. However, using default alternates may be difficult with complex collaboration scenarios. Edits to alternate objects are not know by all systems and, therefore, difficulties can arise. In accordance with one illustrative embodiment, a data compatibility system consists of several extensions to the conventional Commonpoint streaming architecture. The data compatibility system extends the streaming architecture by introducing stream wrappers, bit buckets and enhanced streaming support functions. Some of the stream wrapper classes are public and are available to developers. Others are private and used to implement various internal functions. The function of several stream wrapper classes, including TAlternatelnputStream, TAlternateOutputStream, TExtensioninputStream and TExtensionOutputStream, is to wrap the stream and filter streamed data. For example, TExtensionOutputStream makes sure that every class extension field group is preceded on the stream by its length, and TAlternateOutputStream discards alternates that are not needed. These classes are available to developers because they are intended to be used to support data compatibility. The complete class hierarchy for the public and private streaming classes is shown in FIGS. 5 and 6. FIGS. 5 and 6 are a series of so-called Booch class diagrams illustrating classes which form a variety of different frameworks. Each of the classes and the relationships therebetween will be discussed in detail below. The Booch diagrams and notations used therein are well known to those of ordinary skill in the art and will not be described or explained herein. The interpretation of the Booch diagrams and notations are explained in a text book published by The Benjamin/Cummings Publishing Company, Inc. entitled Object-Oriented Analysis and Design with Applications by Grady Booch, which is hereby incorporated herein by reference. Public Stream Wrapper Classes Referring to FIG. 5, the TDelegatingStream class 502 is an abstract stream class which descends from the abstract class TStream 500 and delegates to another stream. It passes all stream calls though the destination stream. It has the following class declaration:
class TDelegatingStream : public TStream {
public:
TDelegatingStream();
TDelegatingStream(TStream* streamToAlias);
virtual .about.TDelegatingStream();
TStream* GetStream() const;
virtual void SetStream(TStream* streamToAlias);
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
protected:
virtual void SyncDestinationStream(); //Write flags to the
destination stream.
virtual void SyncDestinationStreamBuffers(); //Write buffered
data to
destination stream.
virtual void SyncToDestinationStream(); //Update flags from the
destination stream.
virtual void SyncToDestinationStreamBuffers(); //Update buffers
from the destination
stream.
private:
TDelegatingStream (const TDelegatingStream&);
const TDelegatingStream& operator==(const TDelegatingStream&);
TStream* fDestinationStream;
public:
//All stream operations are pass-through to
fDestinationStream...
}
The TAlternatelnputStream class 508 defines a delegating stream for reading alternates. It works in tandem with the Resurrect( ) function to read a stream containing alternates. It returns the first known alternate and skips all others. The TAlternateInputStream class 508 is not meant to be subclassed. Its declaration is as follows:
class TAlternateInputStream : public TDelegatingStream {
public:
TAlternateInputStream()
TAlternateInputStream(TStream* streamToAlias);
virtual .about.TAlternateInputStream();
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
private:
TAlternateInputStream (const TAlternateInputStream&);
TAlternateInputStream& operator=(const
TAlternateInputStream&);
bool fEnabled;
TBufferedOutputStream* fBufferedOutputStream;
}
The TAlternateOutputStream class 510 defines a designating stream for streaming out alternates. It supports a PrepareForNextAlternate( ) method which can be used to determine whether or not the stream requires alternates. TAlternate OutputStream class 510 is not meant to be subclassed. Its declaration is as follows:
class TAlternateOutputStream : public TDelegatingStream {
public:
TAlternateOutputStream();
TAlternateOutputStream(TStream* streamToAlias);
virtual .about.TAlternateOutputStream();
bool PrepareForNextAlternate(); //Prepare for the next
alternate.
//Returns TRUE if an alternate
is
//required; FALSE otherwise.
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
private:
TAlternateOutputStream (const TAlternateOutputStream&);
TAlternateOutputStream& operator=(const
TAlternateOutputStream&);
bool fEnabled;
TBufferedOutputStream* fBufferedOutputStream;
TStream* fOriginalStream;
}
The TBufferSupportStream class 504 is an abstract stream wrapper which supports buffered input and output streams. It classifies the wrapped stream as being NIL, non-persistent with random access or persistent with sequential access. Its declaration is as follows:
class TBufferSupportStream : public TDelegatingStream {
public:
enum EStreamType {
kNIL, // NIL stream pointer
kLocal, //Non-persistent stream
kRandom, //Random access persistent stream
kSequential //Sequential persistent stream
};
TBufferSupportStream();
TBufferSupportStream(TStream* streamToAlias);
virtual .about.TBufferSupportStream();
virtual void SetStream(TStream* streamToAlias);
EstreamType GetStreamType() const;
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
private:
TBufferSupportStream (const TBufferSupportStream&);
TBufferSupportStream& operator=(const TBufferSupportStream&);
void ClassifyStream();
EStream Type fStreamType;
}
The classes descended from the TBufferSupportStream class 504 are illustrated in FIG. 6. The TBufferedInput Stream class 602 implements an input stream which buffers its data. It is a delegating stream that stores streamed data in a virtual buffer instead of reading it directly from the stream. The TBufferedInputStream class accepts a stream header object that maintains statistics about the buffered data. When reading the wrapped data from the stream, it first reads the header data from the stream before go any of the buffered data. The TBufferedInputStream class 602 also provides a method to skip the wrapped data without reading it from the stream. Its declaration is as follows:
class TBufferedInputStream : public TBufferSupportStream {
public:
TBufferedInput Stream();
TBufferedInput Stream(TStream* streamToAlias, TStreamHeader*
headerToAdopt, TVirtualContext* vcToAdopt);
virtual .about.TBufferedInputStream();
virtual void SetStream(TStream* streamToAlias);
virtual void SetFreezeLevel(FreezeLevel theFreezeLevel);
virtual void SkipBufferedInputData();
const TStreamHeader* GetHeader() const;
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
private:
TBufferedInput Stream (const TBufferedInput Stream&);
TBufferedInputStream& operator = const TBufferedInput
Stream&);
void SetupBuffer();
void CleanupBuffer();
TRandomAccessStream* fLocalStreamPtr;
TVirtualContext* TVirtualContext;
TStreamHeader* header;
TStreamHeader* fNewHeader;
TBufferSupportStream::EStreamType fOriginalStreamType;
}
The TBufferedOutputStream class 604 implements an output stream which buffers its data. It is a delegating stream that stores streamed data in a virtual buffer instead of writing it directly to the stream. TBufferedOutputStream class 604 accepts a stream header object that maintains statistics about the buffered data. When writing the wrapped data to the stream, it first streams the header and then streams the buffered data. The TBufferedOutputStream class 604 also provides a method to dump wrapped data without writing it to the stream. Its declaration is as follows:
class TBufferedOutputStream : public TBufferSupportStream {
public:
TBufferedOutputStream();
TBufferedOutputStream(TStream* streamToAlias, TStreamHeader*
headerToAdopt, TVirtualContext* vcToAdopt);
virtual .about.TBufferedOutputStream();
virtual void SetStream(TStream* streamToAlias);
virtual void SetFreezeLevel(fFreezeLevel the FreezeLevel);
virtual void SetHeader(TStreamHeader* headerToAdopt);
const TStreamHeader* GetHeader()const;
virtual void SetVirtualContext(TVirtualContext* vcToAdopt);
const TVirtualContext* GetVirtualContext() const;
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
private:
TBufferedOutputStream (const TBufferedOutputStream&);
TBufferedOutputStream& operator=(const TBufferedOutputStream&);
void SetupBuffer();
void CleanupBuffer();
TStream* fSavedStream;
TRandomAccessStream* fLocalStreamPtr;
TVirtualContext TVirtualContext;
TStreamHeader* header;
StreamPosition fHeaderPosition;
TBufferSupportStream::EStreamType fOriginalStream type;
}
The TExtensionInputStream class 608 defines a buffered input stream for streaming in extensions. It supports an IsOrthogonal method which tells whether or not the extension is orthogonal to previous data. Its declaration is as follows:
class TExtensionInputStream : public TBufferedInputStream {
public:
TExtensionInputStream()
TExtensionInputStream(TStream* streamToAlias);
virtual .about.TExtensionInputStream();
bool IsOrthogonal() const;
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator>>=(TStream& fromWhere);
private:
TExtensionInputStream (const TExtensionInputStream&);
TExtensionInputStream& operator = (const
TExtensionInputStream&);
}
The TExtensionOutputStream class 612 defines a buffered output stream for writing extensions. It has the following declaration:
class TExtensionOutputStream : public TBufferedOutputStream {
public:
TExtensionOutputStream();
TExtensionOutputStream(TStream* streamToAlias, bool orthogonal
=
FALSE);
virtual .about.TExtensionOutputStream();
private:
TExtensionOutputStream (const TExtensionOutputStream&);
TExtensionOutputStream& operator=(const
TExtensionOutputStream&);
}
Public Bit Bucket Classes The data compatibility support code also includes bit bucket classes which can be used to preserve unread stream data. Applications pass bit bucket objects to the ReadVersion( ) and Resurrect( ) functions to capture skipped extensions and alternates, and applications pass the filled bit buckets to the WriteVersion( ) and Flatten( ) functions to stream the saved data. There are two bit bucket classes, TAlternateBitBucket and TExtensionBitBucket, and they are both unrooted (neither descends from another class.) Their implementations are not public. The TAlternateBitBucket class constructs an object which captures unread alternates. Applications can pass a TAlternateBitBucket object to the Resurrect( ) function, and the Resurrect( ) function will fill it with all of the alternates that it skips. Applications can also pass a TAlternateBitBucket object to the Flatten( ) function which will stream the saved alternates instead of the regular alternates for the flattened object. A TAltemateBitBucket object preserves the order of the alternates, so they can be written in the same order that they were read. The TAlternateBitBucket class is not meant to be subclassed. It has the following declaration:
class TAlternateBitBucket {
public:
TAlternateBitBucket();
TAlternateBitBucket(const TAlternateBitBucket&);
virtual .about.TAlternateBitBucket();
#ifndef NO_Internal
void ReadPriorAlternate(TStream&);
void ReadSubsequentAlternate(TStream&);
void WriteAllPriorAlternates(TStream&);
void WriteAllSubsequentAlternates(TStream&);
#endif
TAlternateBitBucket& operator=(const TAlternateBitBucket&);
private:
#ifndef NO_Internal
class TStreamedObjectAlternate {
public:
TStreamedObjectAlternate();
virtual .about.TStreamedObjectAlternate();
// Streaming operators
virtual TStream& operator>>=(TStream&);
virtual TStream& operator<<=(TStream&);
private:
TAlternateStreamHeader header,
char* fData;
}
TArrayOf<TStreamedObjectExtension> fPriorAlternates;
TArrayOf<TStreamedObjectExtension> fSubsequentAlternates;
#endif
}
The TExtensionBitBucket class constructs an object which captures unread extensions. Applications can pass a TExtensionBitBucket object to the ReadVersion( ) function, and the ReadVersion( ) function will fill the object with all of the skipped extensions. Applications can also pass a filled TExtensionBitBucket object to a WriteVersion( ) function, and the WriteVersion( ) function will stream the saved extensions. A TExtensionBitBucket object maintains a flag which indicates whether or not the extension data it contains is independent of the other stream data for that class. The TExtensionBitBucket class is not meant to be subclassed and is defined as follows:
class TExtensionBitBucket {
public:
TExtensionBitBucket();
TExtensionBitBucket (const TExtensionBitBucket&);
virtual .about.TExtensionBitBucket();
bool IsOrthogonal() const;
#ifndef NO_Internal
void ReadExtensions (long count, TStream&);
void WriteExtensions(TStream&),
long GelExtensionCount() const;
#endif
TExtensionBitBucket& operator = (const
TExtensionBitBucket&);
private:
#ifndef NO_Internal
class TStreamedObjectExtension {
public:
TStreamedObjectExtension();
virtual .about.TStreamedObjectExtension();
//Streaming operators
virtual TStream& operator>>=(TStream&);
virtual TStream& operator<<=(TStream&);
private:
TExtensionStreamHeader header;
char* fData;
}
TArrayOf<TStreamedObjectExtension>fExtensions;
#endif
}
Streaming Support Functions The existing Commonpoint framework provides global functions for monomorphic and polymorphic streaming and stream format version handling as discussed above. The inventive data compatibility system extends these functions to accept bit buckets, handle alternates and deal with the other new compatibility features. For example, the conventional ReadVersion( ) function and the WriteVersion( ) function allow applications to read and write stream format version numbers and have the following form:
void ReadVersion(Tstream& fromWhere, const VersionInfo
oldestSupportedVersion,
const VersionInfo newestSupportedVersion);
void WriteVersion(TStream& toWhere, const VersionInfo version,
long extensionCount = 0);
A data compatibility system constructed in accordance with the principles of the present invention, modifies the ReadVersion( ) function and the WriteVersion( ) function to handle bit buckets for saving and writing extra extension data. These new functions have the form:
void ReadVersion(TStream& fromWhere, const VersionInfo
oldestSupportedVersion,
const VersionInfo newestSupportedVersion,
TExtensionBitBucket& bucket);
void WriteVersion (TStream& toWhere, const VersionInfo version,
long extensionCount, const TExtensionBitBucket&
bucket);
The conventional Flatten( ) and Resurrect( ) functions in the Commonpoint framework do polymorphic streaming as described above and have the following form: template<classAType> void Flatten (const AType* objPtr, TStream& toWhere); template<classAType> void Resurrect(AType&* newObj, TStream& fromWhere); The inventive data compatibility system adds versions of the Flatten( ) and Resurrect( ) functions that accept bit buckets for saving and writing alternates. These new versions have the following form:
template<classAType>
void Flatten(const Atype* objPtr, TStream& toWhere,
const TAlternateBitBucket& bucket);
template<classAType>
void Resurrect(AType&* newObj, TStream& fromWhere, TAlternateBitBucket&
bucket);
The inventive data compatibility system also adds versions of the Flatten( ) function which flatten objects without their alternates. These versions have the following form:
template<classAType>
void FlattenWithoutAlternates(const AType* objPtr, TStream& toWhere);
template<classAType>
void FlattenWithoutAlternates(const AType* objPtr, TStream& toWhere,
const TAlternateBitBucket& bucket);
Private Stream Wrapper Classes The inventive data compatibility system also extends the streaming architecture by enhancing contexts, streams and the streaming support functions and introducing virtual contexts, stream wrappers, stream wrapper headers and bit buckets. Many of these enhancements are necessary to preserve reference information on the stream when some of the referenced objects are not recognized by the stream reader. In particular, each stream has a default context. The context stores references to streamed objects. Whenever an object is streamed using one of the global streaming functions, the streaming function stores a reference to the object in the stream's context. If the object is already in the context, then instead of streaming the object again, the function writes a reference (called an "alias") to the previously-streamed instance of the object. Aliases reduce the stream size and allow a stream writer to preserve reference information in the stream. The operation of aliases is illustrated in FIGS. 7A and 7B. FIG. 7A illustrates streaming without aliases. It shows two objects, 700 and 702, designated A and B, that both refer to the same object 704, designated C. Without aliases, the stream writer would stream object A, which would cause object C to be streamed, and then the stream writer would stream object B, which would cause object C to be streamed again as schematically indicated by arrow 706. At the other end of the data stream, the stream reader would construct an object copy 708, designated A', then an object copy 712, designated C', and construct a reference between the two objects. The reader would then construct an object copy 710, designated as B', then another object copy 714, designated C", and set up the reference. The end result is that objects 708 and 710 point to two different copies of C, objects 712 and 714. The fact that objects 708 and 710 should point to the same object would have been lost. FIG. 7B shows streaming with aliases. In this case, the stream writer writes object 716, designated as A, then object 720, designated as C, then object 718, designated as B and then an alias, designated as C, to the previously streamed version of C. These items are transported on the data stream indicated by arrow 722. At the other end of the data stream, the stream reader would construct an object copy 724, designated A', then an object copy 728, designated C', and construct a reference between the two objects. The reader would then construct an object copy 726, designated as B'. When the stream reader sees the alias it knows to connect object copy 726 to the previously-read version of object C, which is object 728. Thus, the reference is preserved. However, a problem arises when the aliased object is not recognized by the stream reader. In the example illustrated in FIG. 7B above, a problem occurs if the stream reader did not read the streamed version of object 720. This may happen if object 720 was part of an extension or an alternate that was skipped by the reader. Then the subsequent alias to the unrecognized object ( C) would be invalid. In order to prevent this problem from arising, the inventive data compatibility system modifies the stream writer to detect that the referenced object might not be read by some readers. At that point, instead of writing an ordinary alias the stream writer writes a "robust" alias onto the stream. The robust alias contains alias information plus a re-streamed copy of the aliased object. When the robust alias is read, the reader checks if it has seen the aliased object. If it has, it fixes up the pointer and skips over the re-streamed copy. If it has not seen the aliased object, it proceeds to read the re-streamed object from the robust alias. There are several cases where robust aliases are used. For example, robust aliases can be used with class extensions. Since class extensions might not be read by the reader, an alias that the reader encounters is potentially invalid if it refers to class information (which creates an object) that is outside the current class extension or any parent class of the current extension. In this latter case, a robust alias must be written, otherwise, a simple alias can be used. Clearly there can be no aliasing between alternates, since at most one of them will be read. Therefore, when two alternates reference the same object it should be streamed twice. However, if there is an external reference to an object in one of the alternates, then a robust alias must be streamed since that alternate may or may not be read. Another case concerns an external reference to an object that appears in more than one alternate. For example, suppose that two alternates each contain copies of an object, and that an external object also references that object. If the external object references the object in one of the alternates, then that reference will be broken for stream readers that choose the other alternate. In accordance with the principles of the present invention, information about duplicate objects is kept in the alternate wrappers. Each alternate is wrapped like an extension so that its length can precede it on the stream and the duplicate object information is written on the stream along with the length. In the example above, the second alternate can include information that indicates that the object which it references is identical to the object that is referenced by the first object. All readers will see this information because the alternate headers are read automatically. The reader can use this information to update the context and maintain the reference no matter which alternate it chooses. Since this new data is an extension of the existing wrapper class, the new equivalence data must be streamed before the length, (that way it can be skipped by application built with older frameworks). Since the amount of equivalence information is not known beforehand, enough space must be left on the stream to hold the maximum number of duplicates. This maximum number is the number objects that were streamed in all previous alternates. The inventive data compatibility system introduces private stream wrapper classes which construct objects that wrap a given stream and filter the streamed data. For completeness, these classes are included in the stream wrapper class hierarchy illustrated in FIGS. 5 and 6. These private classes include a TRobustAliasInputStream class 606 and a TRobustAliasOutputStream class 610, which handle the streaming of robust aliases, and a TAlternateEnableStream class 506, which is used to optimize alternate streaming as described below. Besides tracking streamed data, wrappers can also optimize streaming. One example is a TAlternateEnableStream constructed from the TAlternateEnableStream class 506. This latter stream is a delegating stream that controls whether or not alternates are streamed. As previously mentioned, the process of constructing and streaming alternates can slow the streaming process considerably, so it is important to avoid streaming alternates wherever possible. Also, as explained above, alternates need only be streamed when the object is being flattened to a persistent stream. Within the object's streaming operator it is possible to determine whether or not the stream is persistent, but it is not possible to determine whether or not the object is being flattened. In order to determine this latter condition, the Flatten( ) function needs to be able to signal the streaming operator that the object is being flattened and not monomorphically streamed. In accordance with the principles of the present invention, the Flatten( ) function does this by wrapping its given stream with a TAlternateEnableStream and passing the latter to the object's streaming operator. This operation is illustrated in FIG. 8. The Flatten( ) function 802 of an object 804 first determines whether alternates are necessary by determining whether the stream is persistent. If alternates are necessary the Flatten( ) function constructs a TAlternateEnableStream from class 806 (which descends from the TDelegatingStream class 800) and wraps the stream with it. If the streaming operator defines alternates, it will contain a TAlternateOutputStream constructed from class 808. At construction time, this TAlternateOutputStream checks the type of the stream. If it is a TAlternateEnable stream then TAlternateOutputStream knows that the object is being flattened and therefore alternates should be streamed. If the stream is not a TAlternateEnableStream then the object is merely being streamed and therefore alternates are not necessary. The TAlternateEnableStream class 506 constructs an internal delegating stream that enables a TAlternateOutputStream object to stream alternates. The TAlternateEnableStream class declaration is as follows:
#ifndef NO_Internal
class TAlternateEnableStream : public TDelegatingStream {
public:
TAlternateEnableStream(TStream* streamToAlias);
virtual .about.TAlternateEnableStream();
}
#endif
The TRobustAliasOutputStream class 610 constructs an object which writes a robust alias to the stream. Its class declaration is as follows:
#ifndef NO_Internal
class TRobustAliasOutputStream : public TDelegatingStream {
public:
TRobustAliasOutputStream();
TRobustAliasOutputStream(TStream* streamToAlias,
ContextObjectIndex);
TRobustAliasOutputStream (const
TRobustAliasOutputStream&);
virtual .about.TRobustAliasOutputStream();
TRobustAliasOutputStream& operator=(const
TRobustAliasOutputStream&);
TStream& operator>>=(TStream&);
TStream& operator<<=(TStream&);
protected:
TRobustAliasHeader header;
TBufferedStream fStreamWrapper;
TVirtualContext TVirtualContext;
}
#endif
The TRobustAliasInputStream class 606 constructs an object which reads a robust alias from the stream. Its declaration is as follows:
#ifndef NO_Internal
class TRobustAliasInputStream : public TDelegatingStream {
public:
TRobustAliasInputStream();
TRobustAliasInputStream(TStream* streamToAlias);
TRobustAliasInputStream (const
TRobustAliasInputStream);
virtual .about.TRobustAliasInputStream();
TRobustAliasInputStream& operator=(const
TRobustAliasInputStream&)
virtual TStream& operator>>=(TStream&);
virtual TStream& operator<<=(TStream&);
protected:
TRobustAliasHeader header;
TBufferedStream fStreamWrapper;
TVirtualContext TVirtualContext;
}
#endif
The private stream wrapper classes are implemented using private stream header classes, which maintain statistics about the wrapped data, such as its length, the number of flattened objects the data contains and whether or not it aliases external objects. Some of this data comes from the stream, some comes from the context and some comes from other places. There are specialized headers to track each type of data. The full stream header class hierarchy is illustrated in FIG. 9. Every set of wrapper classes has its own header class. For example, TAlternatelnputStream objects and TAlternateOutputStream objects use a TAlternateStreamHeader class 902 and TExtensionlnputStream objects and TExtensionOutputStream objects use a TExtensionStreamHeader class 904. Each of the header classes 902 and 904 descend from a common parent class, TStreamHeader 900, and are specialized to track only that stream data which is important to that particular wrapper. The TStreamHeader class 900 is the basic header class. It keeps track of the length of the wrapped data, the number of wrapped objects and whether or not the data is self contained. Its class declaration is as follows:
#ifndef NO_Internal
class TStreamHeader {
public:
TStreamHeader();
TStreamHeader (const TStreamHeader&);
virtual .about.TStreamHeader();
virtual void InitializeHeaderData (const TStream&);
virtual void ComputeHeaderDelta (const TStream&);
virtual void Subtract header (const TStreamHeader&);
virtual void JumpToEnd (const TStream&) const;
StreamPosition GetDataLength() const;
unsigned long GetObjectCount() const;
void SetSelfContained(bool);
bool IsSelfContained() const;
TStreamHeader operator=(const TStreamHeader&);
virtual TStream& operator>>=(TStream&);
virtual TStream& operator<<=(TStream&);
protected:
StreamPosition fInitialStreamPosition;
StreamPosition fStreamPositionDelta;
unsigned long fInitialObjectCount
unsigned long fObjectCountDelta;
bool fSelfContained;
};
#endif
The TAlternateStreamHeader class 902 constructs an object which maintains an array of object indexes which is used to record aliases between alternates. The class declaration is:
#ifndef NO_Internal
class TAlternateStreamHeader : public TContextStreamHeader {
public:
TAlternateStreamHeader();
TAlternateStreamHeader (bool firstAlternate);
TAlternateStreamHeader (const
TAlternateStream Header&);
virtual .about.TAlternateStreamHeader();
virtual void InitializeHeaderData (const TStream&);
virtual void ComputeHeaderDelta (const TStream&);
virtual TStream operator>>=(STREAM&) const;
virtual TStream operator<<=(STREAM&);
protected:
void CleanupArray();
void UpdateArray();
bool fFirst;
TArrayOf<TCollectibleLong>* fAliasArray;
ContextObjectIndex fAlternateStartIndex;
ContextObjectIndex fAlternateObjectCount;
};
#endif
The TExtensionStreamHeader class 904 constructs an object which maintains an orthogonal flag that indicates whether the data contained in this extension is orthogonal to all earlier class data. The class declaration is:
#ifndef NO_Internal
class TExtension Stream Header : public TContextStreamHeader {
public:
TExtensionStreamHeader()
TExtensionStreamHeader (bool orthogonal = false);
TExtensionStreamHeader (const
TExtensionStreamHeader&);
virtual .about.TExtensionStreamHeader();
void SetOrthogonal (bool);
bool IsOrthogonal() const;
TExtensionStreamHeader operator=(const
TExtensionStreamHeader&);
virtual TStream& operator>>=(TStream&) const;
virtual TStream& operator<<=(TStream&);
protected:
bool fOrthogonal;
};
#endif
The TRobustAliasHeader class 906 constructs an object which maintains the ID of the aliased object. The class declaration is:
#ifndef NO_Internal
class TRobustAliasHeader : public TContextStreamHeader {
public:
TRobustAliasHeader()
TRobustAliasHeader(ContextObjectIndex);
TRobustAliasHeader (const TRobustAliasHeader&);
virtual .about.TRobustAliasHeader();
void SetObjectIndex
(ContextObjectIndex);
ContextObjectIndex GetObjectIndex() const;
TRobustAliasHeader operator=(const
TRobustAliasHeader&);
virtual TStream& operator>>=(TStream&) const;
virtual TStream& operator<<=(TStream&);
protected:
ContextObjectIndex fObjectIndex;
};
#endif
FIG. 10 illustrates stream header usage. A TAlternateOutputStream 1000 owns a TAlternateStreamHeader 1006 which it uses to initialize its TBufferedOutputStream 1002. The TBufferedOutputStream 1002 aliases the header and keeps it in synchronization with the buffered data. When it is time to stream out the buffered data, the TBufferedOutputStream 1002 makes sure that the header is written to the stream before the buffered data. Private Stream Context Classes In order to handle the aliasing problem discussed above, the inventive data compatibility system uses a new TContext class that supports nested scopes of visibility between objects. It constructs and maintains a stack of "virtual" contexts that applications can push on the stack and pop off the stack in order to define ranges of visibility. It also stores a visibility flag with each object and provides an IsVisible( ) method that returns the visibility of the given object within the context. The TContext class is a private class that is used to maintain references to stream objects. It is a modification of the TContext class found in the aforementioned Commonpoint framework. The most notable additions for data compatibility are the virtual context stack and the extra object data, both of which are described below. The TContext class also constructs private TContextObjectData object instances in order to maintain object data. The TContextObjectData instances are created and maintained via use of the Add( ), AddAt( ), Find( ), lncrementCount( ), Remove( ) and Reset( ) methods in the TContext object. The class declaration for TContext is:
#ifndef NO_Internal
typedef const void ContextObjectData;
typedef long ContextObjectIndex;
const ContextObjectIndex kInvalidContextObjectIndex;
class TContext {
public:
TContext();
TContext (const TContext&)
virtual .about.TContext();
long GetCount() const;
void IncrementCount (unsigned long);
ContextObjectIndex Add(ContextObjectData objectToAlias,
bool& newEntry);
ContextObjectData AddAt(ContextObjectData objectToAlias,
ContextObjectIndex);
ContextObjectData Find(ContextObjectIndex) const;
void Reset();
void MakeAlias(ContextObjectIndex from,
ContextObjectIndex to);
ContextObjectIndex GetAliasValue(
ContextObjectIndex)const;
void PushVirtualContext(TVirtualContext*
contextToAdopt);
const TVirtualContext* PopVirtualContext();
ContextObjectData Remove(ContextObjectIndex);
bool IsVisible(ContextObjectIndex) const;
bool IsInSiblingContext(
ContextObjectIndex) const;
ContextObjectIndex GetSiblingStartIndex() const;
TContext& operator=(const TContext&);
private:
virtual void AllocateInternalTables();
virtual void CleanupInternalTables();
class TInternalContextObjectData {
public:
TInternalContextObjectData();
TInternalContextObjectData(ContextObjectData*);
TInternalContextObjectData (const
TInternalContextObjectData&);
virtual .about.TInternalContextObjectData();
void Reset();
ContextObjectData* fObject;
bool fVisible;
ContextObjectIndex fAliasTo;
};
TDictionaryOf<TCollectibleLong, ContextObjectIndex>*
fDictionaryOfObjects;
TArrayOf<TInternalContextObjectData>*
fArrayOfObjects;
TVirtualContext* TVirtualContext;
ContextObjectIndex fSiblingStartIndex;
ContextObjectIndex fPreviousSiblingStartIndex;
long fCount;
};
#endif
The TVirtual Context class is an implementation class that is used to define scopes of objects within a context. It is described below and has the class declaration:
#ifndef NO_Internal
class TVirtualContext {
public:
TVirtualContext();
TVirtualContext (bool sibling = false);
TVirtualContext (const TVirtualContext&);
virtual .about.TVirtualContext();
bool IsSibling() const;
virtual void
SetSiblingStartIndex(ContextObjectIndex);
ContextObjectIndex GetSiblingStartIndex() const;
virtual void
SetFirstObjectIndex(ContextObjectIndex);
ContextObjectIndex GetFirstObjectIndex() const;
virtual void SetObjectCount (unsigned long);
TVirtualContext* GetNextVirtualContext() const;
TVirtualContext& operator=(const TVirtualContext&);
virtual TStream& operator>>=(TStream& toWhere) const;
virtual TStream& operator<<=(TStream& fromWhere);
private:
TVirtual Context* fNextVirtualContext;
ContextObjectIndex fSiblingStartIndex;
ContextObjectIndex fStartIndex;
unsigned long fCount;
bool fIsSibling;
bool fIsSelfContained;
}
#endif
The stream compatibility data writers, TExtensionOutputStream and TAlternateOutputStream, discussed above, are coded to manipulate the virtual context stack so that the ranges defined represent the point of view of the receiver. If an object is visible, that means it must have been read by the stream reader. Therefore, the aliasing code in the Flatten( ) function can write simple aliases to visible objects and robust aliases to objects that are not visible. The algorithm for maintaining the visibility flag is straightforward. Each object that is placed in the context is automatically made visible. When a new virtual context is pushed on the stack there is no change. But when a virtual context is popped off of the stack, all of the objects within that virtual context, (i.e. all of the objects that were added to the context while that virtual context was on the stack), are marked invisible. A sequence of operations in which a virtual context is pushed onto and popped off of a stack is illustrated in FIGS. 11A-11F. These figures illustrate the return value of TContext::IsVisible on a stream that is being written. The thick horizontal line at the top of each image, for example line 1100, represents the stream, while the stair-stepped line at the bottom of each image represents the virtual context stack on the stream. Down-pointing arrows, for example arrow 1102, indicate when a virtual context was pushed onto the stack, while up-pointing arrows, for example arrow 1104, indicate when a virtual context was popped off of the stack. Objects are written on the stream from left to right in each diagram, and the right-pointing arrow in each diagram represents the current position of the stream. Hatched areas, such as area 1106, on the stream represent objects that are visible from the current position (IsVisible( ) returns TRUE), while light areas on the stream represent objects that are not visible from the current position (IsVisible( ) returns FALSE). Notice that whenever a virtual context is popped, all of the objects that were written to the stream within that virtual context are marked invisible. In addition to a stack-based parent-child relationship, virtual contexts also support a sibling relationship. Any virtual context which is instantiated with a value of TRUE is declared to be a sibling of the previously-popped virtual context. FIGS. 12A-12H illustrate the return value of TContext::IsInSiblingContext on a stream that is being written. The thick horizontal line, for example line 1200, at the top of each image represents the stream, while the stair-stepped line at the bottom of each image represents the virtual context stack on the stream. Down-pointing-arrows, for example arrow 1202, indicate when a virtual context was pushed onto the stack, while up-pointing arrows, for example arrow 1204, indicate when a virtual context was popped off of the stack. Down-pointing arrows with an S, for example arrow 1206, indicate when a sibling virtual context was pushed onto the stack. Objects are written on the stream from left to right in each diagram, and the right-pointing arrows in each diagram represent the current position of the stream. Hatched areas on the stream, for example, area 1208, represent objects that are in sibling contexts from the current position, while light areas on the stream represent objects that are not in sibling contexts from the current position. Notice how the sibling relationship applies to the previously popped context, whether or not it was a sibling context, and notice that the sibling relationship is transitive. Non-sibling virtual contexts are used to implement extensions. When it is constructed and initialized, a TOutputExtensionStream object pushes a non-sibling virtual context onto the stack. At destruction time, the TOutputExtensionStream destructor pops the virtual context off the stack, which makes all of the extension objects that were added to the context invisible. This indicates that subsequent aliases to those extension objects should be robust. Sibling contexts are used to implement alternates. At the start of the first alternate, a TAlternateOutputStream object pushes a non-sibling virtual context onto the stack and at the end of that alternate, the TAlternateOutputStream object pops the non-sibling virtual context off the stack. At the start of each subsequent alternate, the TAlternateOutputStream object pushes a sibling virtual context onto the stack and at the end of that alternate it pops it off the stack. This allows the internal streaming code to use the InSiblingContext( ) method to determine if a referenced object appears in any previous alternate. If it does, then the streaming code records this information in the alternate header. In addition to the virtual context stack, TContext maintains extra object data. The extra object data consists of a visibility flag and an alias flag and this data is held in an object constructed from a private implementation class called TContextObjectData. TContext sets the visibility flag of each object as described above and provides an IsVisible( ) method to access its value. TContext does not set the alias flag; instead it provides a MakeAlias( ) setter method for this value. TContext::Find uses the alias flag. If the requested object is NIL but has an aliased value, Find( ) will return the aliased value. This is part of the solution to the "aliasing multiple alternates" problem described in a previous section. Streaming Support Functions The ReadVersion( ) and WriteVersion( ) functions are modified to accommodate the inventive stream format extension strategy. The modified WriteVersion( ) function indicates the presence of extensions on the stream and optionally accepts an extension bit bucket which it writes to the stream. The modified ReadVersion( ) function skips unknown extensions, optionally saving them in a bit bucket, and hands back to the application a stream that it can understand. The following is an implementation of the ReadVersion( ) function. It is for illustrative purposes only and is not intended to be limiting:
Version Info
ReadVersion(TStream& fromWhere, const VersionInfo oldestSupportedVersion,
const VersionInfo newestSupportedVersion)
ReadVersion(TStream& fromWhere, const VersionInfo oldestSupportedVersion,
const VersionInfo newestSupportedVersion,
TExtensionBitBucket&bucket)
{
VersionInfo version;
version <<=fromWhere;
//Check for extensions
if (version& kExtensionFlagMask) {
// Clear and ignore any other version flags
version& = .about.kVersionFlagsMask;
//Read the extension count
ExtensionInfo extensions;
extensions <<=fromWhere;
// Check that the version is not too low
if (version < oldestSupportedVersion)
throw TInvalidVersionError();
// Check that the base version is not too high
if ((version - extensions) > newestSupportedVersion)
throw TInvalidVersionError();
// Skip any versions higher than newestSupportedVersion
// >>> If this is the version that has a bit bucket<<<
// Save the extra extension data in the bit bucket
bucket->ReadExtensions(version -newestSupportedVersion,
fromWhere);
// >>> ELSE if this is the version that does not take a bit
bucket <<<
// Skip the extra extensions.
for (; version > newestSupportedVersion; version--) {
TExtensionInputStream ext(fromWhere);
ext.SkipExtensionData();
// >>> ENDIF <<<
}
} else {
// Clear and ignore any other flags
version& = .about.kVersionFlagsMask;
// Check that the version is in range
if( version < oldestSupportedVersion II version >
newestSupportedVersion )
throw TInvalidVersionError();
}
return version;
}
Illustrative pseudo-code for the WriteVersion( ) function is:
void
WriteVersion(TStream& toWhere, const VersionInfo version,
long extensionCount = 0)
WriteVersion(TStream& toWhere, const Version Info version, long
extensionCount,
const TExtension BitBucket& bucket)
{
// Increment the versions if there are saved extensions.
if (bucket) {
extensionCount += bucket.ExtensionCount();
version += bucket.ExtensionCount();
}
// Throw an exception if the version number interferes with
// the version flags.
if (version& kVersion FlagsMask) {
throw TInvalidVersionError();
} else {
if (extensionCount > 0) {
// Write the version number and the extension count.
version I= kExtensionFlagMask;
version >>= toWhere;
extensionCount >>= toWhere;
// >>> IF this is the version that has a bit bucket <<<
// Write any saved extensions.
bucket->WriteExtensions(toWhere);
// >>> ENDIF <<<
} else {
version >>= toWhere;
}
}
}
As previously mentioned, the Flatten( ) and Resurrect( ) functions perform polymorphic streaming. The following are high-level descriptions of the operations of these functions:
template <class AType>
void Flatten (const AType* objPtr, TStream& toWhere)
{
if (objPtr == NIL) {
// Write a NIL tag to the stream...
} else {
// Wrap the stream with a TAlternateEnableStream to enable
alternates...
// Add objPtr to the context..
// Check the result.
if (objPtr was already in the context) {
if (objPtr is visible in the context) {
// Write a simple alias to the stream...
} else {
// Write a robust alias to the stream...
}
} else {
//Write the object to the TAlternateEnableStream(object tag,
//type info, object data)...
}
}
}
An illustrative high-level description of the resurrect function is given by the following:
template <class AType>
void Resurrect(AType*& newObj, STREAM& fromWhere)
{
if (NIL tag is on the stream) {
return NIL;
} else {
// Process alternates until one is recognized or end of the list
is reached.
while (not done) {
// Read the header tag . . .
switch (on the header type) {
case (start alternates tag):
haveAlternates = TRUE;
break;
case (robust alias tag):
// Check the context . . .
if (the object is in the context) {
// Skip the copy of the object in the alias . . .
// Check the type of the object in the context . . .
if (bad type) {
// Skip all remaining alternates . . .
throw TTypeException(
kObjectDoesNotDeriveFromBase)
}
// Set newObj to point to the object in the context . . .
done = TRUE;
break;
}
// The object is not in the context, so drop through into the
//object tag case to read the object out of the robust alias.
case (object tag):
// Read the object type . . .
// Check the type . . .
if (bad type) {
if (no alternates)
throw TTypeException(
kUnknownLibraryForClass)
// Skip this alternate . . .
break;
}
// Construct and read in the object . . .
// Add the object to the context . . .
done = TRUE;
break;
case (simple alias tag):
//Check the context . . .
if (the object is in the context) {
// Check the type of the object in the context . . .
if (bad type ) {
// Skip all remaining alternates . . .
throw TTypeException(
kObjectDoesNotDeriveFromBase)
}
// Set newObj to point to the object in the context . . .
done = TRUE;
break;
}else {
// The object was not in the context.
// Skip all remaining alternates . . .
throw TTypeException(kObjectNotFoundIn
Context);
}
done = TRUE;
break;
case (end of alternates tag):
// No valid alternates.
throw TTypeException(kUnknownLibraryForClass);
}
// Skip all remaining alternates . . .
}
}
}
As mentioned in the public section discussion of the Flatten( ) and Resurrect( ) functions, there are also versions which accept bit buckets and which flatten an object without its alternates. These functions inhibit the object from streaming its alternates by failing to wrap the given stream with a TAlternateEnableStream object. This disables the TAlternateOutputStream object in the object's stream-out operator, since the a TAlternateOutputStream object will only accept alternates if the type of the stream it wraps is a TAlternateEnableStream. With TAlternateOutputStream disabled, only the first alternate (the object itself) is written to the stream.
|
Same subclass | ||||||||||
