home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
opendc12.zip
/
od124os2.exe
/
od12osr1.exe
/
src
/
DynValus.c
< prev
next >
Wrap
Text File
|
1997-03-21
|
90KB
|
1,705 lines
/* @(#)Z 1.4 com/src/cm/DynValus.c, odstorage, od96os2, odos29712d 97/03/21 17:19:35 (96/10/29 09:17:53) */
/*====START_GENERATED_PROLOG======================================
*/
/*
* COMPONENT_NAME: odstorage
*
* CLASSES: none
*
* ORIGINS: 82,27
*
*
* (C) COPYRIGHT International Business Machines Corp. 1995,1996
* All Rights Reserved
* Licensed Materials - Property of IBM
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
* IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
* USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
* OR PERFORMANCE OF THIS SOFTWARE.
*/
/*====END_GENERATED_PROLOG========================================
*/
/*
File: DynValus.c
Contains: xxx put contents here xxx
Written by: Ira L. Ruben
Owned by: Ed Lai
Copyright: ⌐ 1992-1994 by Apple Computer, Inc., all rights reserved.
Change History (most recent first):
<2> 8/26/94 EL #1181622 Ownership update.
<1> 2/3/94 EL first checked in
To Do:
*/
/*---------------------------------------------------------------------------*
| |
| <<< DynValus.c >>> |
| |
| Container Manager Dynamic Value Routines |
| |
| Ira L. Ruben |
| 5/5/92 |
| |
| Copyright Apple Computer, Inc. 1992-1994 |
| All rights reserved. |
| |
*---------------------------------------------------------------------------*
Dynamic values are special values which "know" how to manipulate on their own value data.
They do this through a set of value operation handlers which are expected to be
semantically the same as the standard API value operations.
The following value operations are expected to be supported by these handlers:
CMSize CMGetValueSize(CMValue value);
CMSize CMReadValueData(CMValue value, CMPtr buffer, CMCount offset, CMSize maxSize);
void CMWriteValueData(CMValue value, CMPtr buffer, CMCount offset, CMSize size);
void CMInsertValueData(CMValue value, CMPtr buffer, CMCount offset, CMSize size);
void CMDeleteValueData(CMValue value, CMCount offset, CMSize size);
void CMGetValueInfo(CMValue value, CMContainer *container, CMObject *object,
CMProperty *property, CMType *type, CMGeneration *generation);
void CMSetValueType(CMValue value, CMType type);
void CMSetValueGeneration(CMValue value, CMGeneration generation);
void CMReleaseValue(CMValue);
Whenever a dynamic value is passed to a standard API value routine corresponding to the
above handlers, the handler is called instead. That is why the handler must be
semantically identical.
Basically, dynamic values are similar to C++ objects where the handlers are the object's
methods. They are dynamic in the sense that they only exist from creation (discussed
below) and last until until they are released (CMReleaseValue()).
A dynamic value can have its own data (similar to C++ class fields). The data is
generally used to create the handler's refCon during dynamic value creation.
A dynamic value results when a value is created by to CMNewValue() or attempted to be
used by CMUseValue(), and the following two conditions occur:
1. The type, or any of its base types (if any, created by CMAddBaseType()), are for
global names that have associated metahandlers registered by CMRegisterType().
2. The metahandlers support the operation type to return a "use value" handler, and
in addition for CMNewValue(), a "new value" handler.
The "new value" handlers are used to define initialization data for the "use value"
handlers. The "use value" handlers are called to set up and return refCons. Another
metahandler address is also returned. This is used to get the address of the value
operation handlers corresponding to the standard API CM... value routines mentioned above.
Just like C++, dynamic values may be "subclassed" via their (base) types. If a handler
for a particular operation is not defined for a value, its "base value" is used to get the
"inherited" handler. This continues up a dynamic value's chain of base values, up to the
original "real" value that spawned the base values from the CMNewValue() or CMUseValue().
Thus for each dynamic value, there is a vector of handler addresses. DynValus.h
defines the layout of that vector. Also defined there are the other interfaces needed to
process a dynamic value by the API routines themselves. The routines themselves are in
this file.
The vector becomes part of a set of "extensions" to the dynamic value header. The
extensions struct is defined in TOCEnts.h along with the other value header
definitions. The dynamic value stuff is kept separate because it is sufficiently
distinct.
The Data Structures for Dynamic Values
--------------------------------------
The following diagram shows the pointer relationships among the various data structures
descending from an object for dynamic values. Most of this is basically the same as
shown in TOCEntries.[ch], so some of the superfluous pointers are removed to just show
the pointers of interest.
*----------* *--------------------* *---------------------*
| *Object* |<--->| *Property1* |<------------->| *Property2* |
*----------* +->| (for these values) | +-->| (dynamic values) |
| *--------------------* | *---------------------*
| |
| |
+-->*-------------------* +-->*--------------------*
| *ValueHdr1* |<--------------| *Dynamic ValueHdr* |
| (a "real" value) |----+ +---->| (bottom layer) |
+-->| (value not shown) | | | +->|(on property chain) |
| *-------------------* | | | *--------------------*
| | | | | *extensions* |
| | | | |[DynamicValueVector]|
| | | | *--------------------*
| | | |
| | | +---------------*--------------------*
| | | | *Dynamic ValueHdr* |
| | | +--->| (middle layer) |
| | | | *--------------------*
| | | | | *extensions* |
| | | | |[DynamicValueVector]|
| | | | *--------------------*
| | | |
| | | +---*---------------------*
| +---------------------->| *Dynamic ValueHdr* |
| | | (top layer) |
| | *--------------------*
| | | *extensions* |
| | |[DynamicValueVector]|
| | *--------------------*
| |
| |
| *-------------------* | *--------------------*
+-->| *ValueHdr2* | +---->| *Dynamic ValueHdr* |
| (a "real" value) |<------------->| (single layer) |
| (value not shown) | |(on property chain) |
*-------------------* *--------------------*
| *extensions* |
|[DynamicValueVector]|
*--------------------*
As usual, for each object we have a list of properties, and each property has a list of
value headers (the value segment lists are not shown). Real values that spawn dynamic
values cause a special property used only to contain the dynamic value headers.
In the above diagram there are two "real" values that spawned dynamic values; "ValueHdr1"
and "ValueHdr2". "ValueHdr1" generated a three-layer dynamic value. This results from
the subclassing of types (subclassing is discussed later). "ValueHdr2" has only a single
layer dynamic value.
The link structure is such that the "real" value always points to the top-most dynamic
value layer. That, in turn, points to its base value. This continues until the bottom-
most base value. It always points back to the "real" value.
Note, the bottom-most layer (the one who has the "real" value as its base) is the ONLY
value header on the dynamic property chain. All higher layers for it are NOT part of the
property chain.
Note also that dynamic value headers never have value segment lists. No data is ever
written to a dynamic value because these headers are removed when the value is released
using a CMReleaseValue(). If there is any data, it must be associated with a "real"
value -- the real value associated with a dynamic value or some place else (how this can
happen is described later).
In each value header there is a pointer (a union called "dynValueData" with alternative
fields called "dynValue" and "extensions") that contains three possible values:
1. dynValueData is NULL for "real" value headers that don't have a corresponding
dynamic value.
2. dynValueData.dynValue is a pointer to the top-most layer if it is a "real" value
that does have a corresponding dynamic value header.
3. dynValueData.extensions is a pointer to the extensions if it is itself a dynamic
value header.
The value header's flags determine how to interpret the pointer.
The extensions is the interesting part. It contains the vector of function pointers to
the value handlers mentioned earlier (not unlike a C++ v-table).
When a dynamic value is spawned by CMNewValue() or CMUseValue(), the pointer to the top-
most dynamic value header is returned as the refNum. That means whenever the user passes
it to an API value routine, it will check to see if the refNum is a dynamic value. If it
is, it initiates the call to the corresponding value handler using the vector in the
extensions. That may cause a search up the base value chain to look for the "inherited"
value routine. In the limit, we end up using the original API value routine if no handler
is supplied and we reach the "real" value in the chain. That's how data could get in
there.
Assuming you're still with me, the following diagram is provided to simplify the
notation. We use this simplification of the diagram above so you can understand the
meaning. We will need this to notation to describe how dynamic values are spawned and
layers created. There is no need now to show all the details as above.
*---* *---* *-----*
| O |-----| P |------| DVP |
*---* *---* *-----*
| |
| |
*-----* *-----* *-----* *-----*
| VH |<----| DVH |<------| DVH |<------| DVH |
*-----* *-----* *-----* +-->*-----*
| | | |
| | | |
| +-----------------------------+
| |
| |
*-----* *-----*
| VH |<--->| DVH |
*-----* *-----*
Here "O" is object, "P" is property, "VH" real value header, "DVP" the dynamic value
property, and "DVH" a dynamic value.
Spawning Dynamic Values
-----------------------
When a CMNewValue() or CMUseValue() is almost done, a check is made on the value's type,
and all of its base types (if any) to see if it has an associated registered metahandler.
If it does, it is called with a "use value" operation type to see if a "use value" handler
exists for the type. If it does, we spawn the dynamic value.
To summarize, if CMNewValue() or CMUseValue() sees any (base) type that has an associated
"use value" handler, it will spawn a dynamic value.
The spawning is done, leaving out some confusing details, by calling the "use value"
handler. It is expected to set up a refCon to pass among the value handlers and a pointer
to another metahandler. These are returned to CMNewValue() or CMUseValue() which uses
newDynamicValue() (defined in this file) to do the actual creation of the dynamic value.
The extensions are initialized, the metahandler pointer saved, and also the refCon. The
pointer to the created dynamic value header is what CMNewValue() or CMUseValue()() returns
to the user as the refNum.
Now, when the user attempts to do a value operation using this refNum, a check is made
that the refNum is for a dynamic value. If it is, the corresponding handler routine will
be called. The vector entries are set on first use of a value operation. It may mean
searching up the base value chain, but once found, we save the handler address in the top
layer vector (associated with the refNum) so we don't have to do the search again.
Note that if we indeed do have to search up the base value chain then we must save the
dynamic value refNum (pointer) in addition to the handler address. This is very much like
C++ classes, where inherited methods are called and the appropriate "this" must also be
passed. The "this" in this case is the refNum.
Layering Dynamic Values
-----------------------
The best way to describe layering is in terms of C++. Say we have the following class
types (using a somewhat abbreviated notation -- don't get picky here):
class Layer1 { // a base class
<layer1 data> // possible data (fields)
Layer1(<layer1 args>); // constructor to init the data
other methods... // value operations in our case
};
class Layer2 { // another base class
<layer2 data> // possible data (fields)
Layer2(<layer2 args>); // constructor to init the data
other methods... // value operations in our case
};
class T: Layer1, Layer2 { // the class of interest!
<T data> // possible data (fields)
T(<T args>, <layer1 args>, <layer2 args>); // constructor to init the data and bases
other methods... // value operations in our case
};
In Container Manager terminology, T is to be a registered type with other registered
types as base types (classes). All type objects are created using the standard API call
CMRegisterType(). Base types can be added to a type by using CMAddBaseType(). This
defines a form of inheritance like the C++ classes.
Type T would be registered with its base types as follows:
layer1 = CMRegisterType(container, "Layer1");
layer2 = CMRegisterType(container, "Layer2");
t = CMRegisterType(container, "T");
CMAddBaseType(t, layer1);
CMAddBaseType(t, layer2);
Internally, the t object looks something like the following:
*---* *-------* *-------*
| t |-----| P[gn] |---------| P[bt] |
*---* *-------* *-------*
| |
| |
*-----* *---* *-----* *--------* *--------*
| VH |--|"T"| | VH |--|"layer1"|--|"layer2"|
*-----* *---* *-----* *--------* *--------*
The value data segments are shown here with the data the segment will point to in the
container.
For the t object, the global name property and value are created as usual by
CMRegisterType(container, "T"). The CMAddBaseType() calls add the base types. These are
recorded as the object ID's for each base type in the order created as separate value
segments for a special "base type" property belonging to the type object.
As mentioned above, CMNewValue() or CMUseValue() spawn dynamic values if the original type
or any of its base types have an associated "use value" handler. Assume that was done for
"T" in the above example. What happens is that CMNewValue() or CMUseValue() will look at
its type object (t here) to see if the base type property is present. If it is, it will
follow each type "down" to leaf types using a depth-first search.
In the example, "layer1" will be visited, then "layer2", and finally the original type
"T" itself. If the "layer1" type object had base types of its own, they would be visited
before using "layer1" itself. Hence the depth-first search down to the leaf types.
For each type processed, if it has a "use value" handler of its own, it will be called
to get a refCon and value handler metahandler. These are passed to newDynamicValue()
to create a dynamic value for the original "real" value. newDynamicValue() always
returns its refNum which will be the dynamic value it created. The first layer will
create the dynamic value property and put the dynamic value header on its value header
list. All further calls to newDynamicValue() will pass the most recent refNum returned
from it. newDynamicValue() then chains these off the first dynamic value header. This
produces the desired layering result.
Note that this scheme allows total freedom for the user to mix types. For example, type
T1 could have base types T2 and T3. Alternatively, T1 could just have base type T2 and
T2 have T3 as its base type!
Data For Dynamic Values
-----------------------
In the C++ class types shown above, note that each class could have its own data along
with its own constructor. The T class has a constructor that calls the constructors of
all of its base classes. We can carry this simile with the Container Manager just so far!
Here's is where it starts to break down.
The problem here is that C++ class types are declared statically. A C++ compiler can see
all the base classes and can tell what data gets inherited and who goes with what class.
In the Container Manager, all "classes" (i.e., our type objects) are created dynamically!
So the problem is we need some way to tell what data "belongs" to what type.
The solution is yet another special handler, which returns a "format specification",
called "metadata". The handler is the "metadata" handler whose address is determined
by the Container Manager from the same metahandler that returns the "new value" and "use
value" handler addresses.
Metadata is very similar to C printf() format descriptions, and as will be described
shortly, for similar purposes. The next section will describe the metadata in detail.
But, for now, it is sufficient to know that it tells CMNewValue() how to interpret its
"..." parameters. The rest of this section will discuss how this is done to dynamically
create data.
As with C++ classes, the data is created when a new value is created, i.e., with a
CMNewValue() call. The data will be saved in the container, so CMUseValue() uses the
type format descriptions to extract the data for each dynamic value layer (actually,
cmFollowTypes() in this file which is called by CMNewValue()).
CMNewValue() is defined as follows:
CMValue CMNewValue(CMObject object, CMProperty property, CMType type, ...);
The "..." is an arbitrary number of parameters used to create the data. Metadata,
accessed from the "metadata" handler, tells CMNewValue() how to interpret these parameters
just like a printf() format tells it how to use its arguments.
The ORDER of the parameters is important! Because base types are done with a depth-first
search through the types down to their leaves, the CMNewValue() "..." parameters MUST be
ordered for the "deepest" type first. For example, given the following type inheritance
hierarchy (read these as T1 inherits from T2 and T3, and so on):
T4 T5
* *
* *
T2 T3
* *
* *
T1
The depth-first search, starting at T1, yields the sequence: T2 T4 T5 T3 T1. Then this
is the order the CMNewValue() "..." parameters must be in.
Note what's happening here is you are supplying all the constructor data just like T
constructor class example above.
The way the data gets written is with special handler, called the "new value" handler.
After CMNewValue() calls the "metadata" handler, it uses the metadata to extract the next
set of CMNewValue() "..." parameters. CMNewValue() then passes the parameters along in
the form of a data packet to the "new value" handler. The "new value" handler is then
expected to use this data (which it can extract with CMScanDataPacket()) to WRITE data
to its BASE value. It is the data written by the "new value" handler that the "use value"
handler will read to create its refCon.
Only CMNewValue() does this. The "new value" handler is only for new values, but the "use
value" handler is used by both CMNewValue() and CMUseValue().
In the simplest case, with only one dynamic value, you can see that the data is written
to the "real" value. Now if you layer another dynamic value on to this, the next chunk
of data is written using that layer's base value and hence through its handlers. The
second layer will thus use the first layers handlers. That may or may not end up writing
to the "real" value depending on the kind of layer it is. If it's some sort of indirect
handler (i.e., it reads and writes somewhere else), the second layer data will probably
not go to the "real" value.
The "use value" handler is called both for CMNewValue() and CMUseValue(). The "use value"
handler reads the data from its base value to create its refCon. If the user comes back
the next day and does a CMUseValue(), only the "use value" handler is called. Again
it reads the data from its base value to construct the refCon and we're back as we were
before in the CMNewValue() case.
It should be pointed out here that the "matadata" and "new value" handlers will always
be executed with a Container Manager running on some particular hardware (obviously). The
data packet built from the CMNewValue() "..." parameters is stored as a function of the
hardware implementation on which it is run (i.e., whatever the sizes are for bytes, words,
longs, etc.). How it is stored is a function of the metadata returned from the "metadata"
handler. In other terms, the "new value" handler has a "contract" with both the Container
Manager and the "metadata" handler on the meaning of the parameter data.
Note, however, it is NOT required that you be on the same hardware when you come back the
next day and call CMUseValue() that leads to the "use value" handler call. The handler
writer must keep this in mind. Specifically, the "use value" handler MUST know the
attributes (bytes size, big/little endian, etc.) of the data written out by the
"new value" handler so it knows how to use that info. In other words, the "use value"
handler has a (separate) "contract" with its own "new value" handler on the meaning of the
data written to the base value.
There is another, relatively minor, thing to keep in mind. That is that the value
handlers for any one layer must take into account the size of its own data when
manipulating additional data created by its handlers for CMReadValueData(),
CMWriteValueData(), etc. This simply offsets the write and read value data operations
by the proper amount. Remember all operations are on their base values. So if a "new
value" handler writes data, this basically prefixes the "real" stuff being written by the
handler operations.
Metadata
--------
As mentioned above, the metadata directs CMNewValue() on how to interpret its "..."
parameters to build data packets passed to "new value" handlers.
The format string is a sequence of characters containing data format specifications.
Unlike printf(), anything other than the data format specifications are ignored. They
are assumed to be comments.
The data format specifications indicate to CMNewValue() how to interpret its data
initialization parameters. Each specification uses (consumes) the next corresponding
"..." parameter to CMNewValue(). This is similar to the behavior of printf().
A data format specification begins with a "%" sign. Immediately following the % is a
required data size layout specification, expressed as a sequence of characters. The data
size specification sequences are as follows:
c A character or byte [note 1].
d A short [note 1].
l[d] A long (the "d" is optional) [note 1].
[*]s A C string (i.e., null delimited). Optionally a "*" indicates that only the
first n characters of the string are to be used. The "*" consumes an
additional CMNewValue() "..." parameter of type CMSize [notes 3, 4, and 5].
i An object, property, or type ID. This is defined as the same size as "ld"
[note 1].
p A pointer [notes 1 and 2].
o A CMObject, CMProperty, or CMType object refNum. This is defined as the
same size as "p" [notes 1 and 2].
v A CMValue refNum. This is defined as the same size as "p" [notes 1 and 2].
Notes:
1. The CMNewValue() "..." parameters are converted to a packet of data using the
hardware implementation defined sizes for bytes, words, longs, etc., as directed by
the metadata returned from the "metadata" handler. Thus the "metadata" handler has
a contract with the "new value" handler that CMNewValue() calls. The data that the
"new value" handler writes to its base value is in terms of a "contract" it has with
its "use value" handler. It is the one that will read that base value data to
create its refCon. If CMUseValue() is expected to be run on different hardware with
different byte sizes, endianess, etc., then that is between the "new value" handler
and its "use value" handler. The Container Manager is independent of that.
2. Pointers can be passed to CMNewValue() to convey special information to the "use
value" handler. You shouldn't, of course, write these as data. RefNums can be
passed to extract object ID's or other read value data. It is permissible to
write object ID's to data. But this will put a restriction on such references that
they shouldn't be moved or deleted.
3. For "%*s", the value corresponding to the "*" is copied to the packet data
immediately in front of the string. This is somewhat (not quite) equivalent to
"%l%s", where the %l is the length, n, and %s is an n byte string. Note however,
this string is NOT null delimited.
4. Caution, the string will be COPIED from the string pointed to in the CMNewValue()
"..." parameter list to the packet. It you intend to pass a pointer to the string,
rather than the string itself, %p should be used. Frankly, it is expected that %s
will not be used much!
5. For symmetry, CMScanDataPacket() returns the value of string length to an explicit
distinct parameter pointer. Thus the parameter pointer list passed to
CMScanDataPacket() should be identical to the "..." parameters passed to a
CMNewValue() "..." parameter list (at least the portion corresponding to this type).
The "Metadata", "New Value", and "Use Value" Handlers
-----------------------------------------------------
The "metadata" handler is only needed for CMNewValue() so that the proper number of
CMNewValue() "..." parameters can be placed into a data packet for the "new value"
handler.
The "metadata" handler should have the following prototype:
CMMetaData metaData_Handler(CMType type);
where,
type = the (base) type layer whose metadata is to be defined
The "metadata" handler simply returns a C string containing the metadata using the format
descriptions described above.
The type is passed as a convenience. It may or may not be needed. It is possible for
a type object to contain OTHER data for other properties. Types, after all, are ordinary
objects. There is nothing prohibiting the creation of additional properties and their
values. This fact could be used to add additional (static and private) information to a
type to be used elsewhere.
Note, as in printf(), if the metadata handlers "lie" about the metadata format, or if
there aren't enough parameters supplied to CMNewValue(), the results will be
unpredictable!
The "new value" handler should have the following prototype:
CMBoolean newValue_Handler(CMValue baseValue, CMType type, CMDataPacket dataPacket);
where,
baseValue = the base value which is to be used to write the refCon data for the
"use value" handler
type = the type corresponding to this "new value" handler
dataPacket = the pointer to the data packet, created from the CMNewValue() "..."
parameters according the types metadata format description
The type is passed again as a convenience just as in the "metadata" handler. It can also
be used here to pass to CMScanDataPacket() to extract the dataPacket back into variables
that exactly correspond to that portion of the CMNewValue() "..." parameters that
correspond to the type. It is not required, however that CMScanDataPacket() be used.
The "use value" handler is called for both the CMUseValue() and CMNewValue() cases. If
it's companion "new value" handler wrote data to its base value, the "use value" handler
will probably read the data to create its refCon. The refCon will be passed to all
value handlers. The "use value" handler returns its refCon along with another
metahandler address that is used to get the value operation handler addresses. These are
then used to create the dynamic value.
The "use value" handler should have the following prototype:
CMBoolean useValue_Handler(CMValue baseValue, CMType type, CMMetaHandler *metahandler,
CMRefCon *refCon);
where,
baseValue = the base value which is to be used to write the refCon data for the
"use value" handler.
type = the type corresponding to this "new value" handler
metahandler = a pointer to the value operations metahandler which is RETURNED by the
"use value" handler to its caller
refCon = a reference constant built by the "use value" handler and RETURNED to
its caller
The baseValue and type are identical to the ones passed to the "new value" handler. The
type may or may not be needed in the "use value" handler. Like the "use value" handler,
it could be used to supply additional information from other type object properties.
It is expected that the "use value" handler will read data from its base value to
construct its refCon. The refCon is then returned along with a pointer to another
metahandler that is used by the Container Manager to get the addresses of the value
operation routines.
Note, both the "new value" and "use value" handlers return a CMBoolean to indicate
success or failure. Failure means (or it is assumed) that the handlers reported some
kind of error condition or failure. As documented, error reporters are not supposed to
return. But in case they do, we use the CMBoolean to know what happened. It should
return 0 to indicate failure and nonzero for success.
Value Operation Handlers
------------------------
The value operation routines can do a CMGetValueRefCon() on the value passed to get at the
refCon set up by the "use value" handler. This provides a communication path among the
value handlers. Further, the value handler should usually do their operations in terms of
their base value. This is accessed using CMGetBaseValue() on the passed value.
There is one exception to this rule; the release handler. A set of one or more dynamic
value layers are spawned as a result of a single CMUseValue() or CMNewValue(). The layers
result from the specified type having base types. From the caller's point of view s/he
is doing one CMUseValue() or CMNewValue() with no consideration of the base types. That
implies that the returned dynamic value should have a single CMRelaseValue() done on it.
The handlers have no business doing CMReleaseValue() on their base value. This is
detected and treated as an error.
A count is kept by the Container Manager of every CMUseValue() and CMNewValue(). Calling
CMReleaseValue() reduces this count by one. When the last release is done on the dynamic
value (its count goes to 0), the release handler will be called. It is the Container
Manager who calls the release handler for all the layers, not the handler. The Container
Manager created them as a result of the original type. It is therefore responsible for
releasing them.
The reason the Container Manager is so insistent on forcing a release for every use of a
dynamic value is mainly to enforce cleanup. Most value operation handlers will, at a
minimum, use a refCon that was memory allocated by the "use value" handler. Release
handlers would then be responsible for freeing that memory. In another example, if any
files were open by the "use value" handler, the releases would close those files.
Note, if all a value operation does is get its base value and call back the API routine
to do its operation (again except for the release handler), then what it is basically
doing is invoking the "inherited" method ('er, sorry, value operation). In this case,
the value operation could be OMITTED entirely. That is, don't define a routine address
in the value operations metahandler (returned by the "use value" handler). The Container
Manager uses that as the signal to search up the dynamic value inheritance chain to find
the first handler defined for the routine. In the limit, it will end up using the
original "real" value.
Possible Limitations and Considerations On Value Operations
-----------------------------------------------------------
As presented, the model for type inheritance described above only allows for inheriting
value operations that would otherwise be processed via the API. A non-obvious fact of
this is that value operations are basically limited to stream operations. That is,
you read or write a chunk of stuff linearly from a specified offset. In addition, there
are insert and delete operations (CMInsertValueData() and CMDeleteValueData()). These two
cause potential problems!
The reason they are a problem is that base types may want to do certain transformations
on their data that depend on what has occurred previously in that stream of data. For
example, encryption, using a cyclic key, or compression, which can be thought of as a form
of encryption, generally cannot be done simply by looking at a chunk of data starting at
some random offset. A cyclic key encryption can be deterministic if you can always
determine where to start in the key as a function of offset. But you can see that inserts
and deletes will change the offsets of following data. You would not know where to start
in the key.
What all this means is that certain data transformations only make sense if you are
willing to refuse to support the insert/delete operations. Basically only data
transformations that are position independent can be fully supported.
Even simple I/O to a file may create problems, since most file systems do not support
inserts and deletes in the middle of a file. If you do want to support inserts and
deletes, then you should consider the potential for data intensive and/or computationally
intensive operations.
Consider yourself warned!
Well, that's all there is to dynamic values. Aren't they fun? I am sorry about this
long winded explanation, but there is no short way to describe this stuff. Would you
rather I didn't?
*/
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#ifndef __CMTYPES__
#include "CMTypes.h"
#endif
#ifndef __CM_API_TYPES__
#include "CMAPITyp.h"
#endif
#ifndef __CM_API__
#include "CMAPI.h"
#endif
#ifndef __LISTMGR__
#include "ListMgr.h"
#endif
#ifndef __TOCENTRIES__
#include "TOCEnts.h"
#endif
#ifndef __TOCOBJECTS__
#include "TOCObjs.h"
#endif
#ifndef __GLOBALNAMES__
#include "GlbNames.h"
#endif
#ifndef __CONTAINEROPS__
#include "Containr.h"
#endif
#ifndef __HANDLERS__
#include "Handlers.h"
#endif
#ifndef __DYNAMICVALUES__
#include "DynValus.h"
#endif
#ifndef __SESSIONDATA__
#include "Session.h"
#endif
#ifndef __ERRORRPT__
#include "ErrorRpt.h"
#endif
#ifndef __UTILITYROUTINES__
#include "Utility.h"
#endif
CM_CFUNCTIONS
/* The following generates a segment directive for Mac only due to 32K Jump Table */
/* Limitations. If you don't know what I'm talking about don't worry about it. The */
/* default is not to generate the pragma. Theoritically unknown pragmas in ANSI won't */
/* choke compilers that don't recognize them. Besides why would they be looked at if */
/* it's being conditionally skipped over anyway? I know, famous last words! */
#if CM_MPW
#pragma segment CMValueOps
#endif
/* Note, this is the same segment used by CMValOps.c intentionally. */
/* For generality (or whatever), the following define all the fields of a metadata */
/* format description... */
#define Meta_Trigger '%' /* all formats are triggered by this */
#define Meta_Ptr 'p' /* pointers */
#define Meta_Object 'o' /* CMObject, CMProperty, or CMType object refNum */
#define Meta_Value 'v' /* CMValue refNum */
#define Meta_ID 'i' /* object, property, or type ID */
#define Meta_Char 'c' /* character or byte */
#define Meta_Short 'd' /* short */
#define Meta_Long 'l' /* long ("%ld" also allowed) */
#define Meta_LongSuffix 'd' /* optional "long" suffix */
#define Meta_Star '*' /* string of n characters (uses as "*s") */
#define Meta_String 's' /* C string */
/*-------------------------------------------------------------------------------*
| hasUseValueHandler - check a base type to see if it has a "use value" handler |
*-------------------------------------------------------------------------------*
This routine takes a value (header) checks to see if the value type is a global name. If
it is (what else?), and if there is a metahandler associated with the name (as defined by
an earlier CMSetMetaHandler() call), then that metahandler is called with a "use value"
operation type. If the methandler returns the address of a "use value" handler, the
function returns that address as its result. If newValueHandler is not NULL then the
"new value" handler address AND a "metadata" handler address are also required and
returned. NULL is returned if the metahandler or "use value" handler do not exist.
Note, the "new value" and "metadata" handlers are requested only for CMNewValue() creation
of dynamic values. That is the only time newValueHandler is specified. Thus an error
will be reported from here, and SessionSuccess (a session global status switch) returned
false, if the "new value" or "metadata" handler is not present when requested. Otherwise
SessionSuccess is always retured as true.
*/
static CMHandlerAddr CM_NEAR hasUseValueHandler(TOCValueHdrPtr baseValueHdr,
TOCObjectPtr type,
CMHandlerAddr *newValueHandler,
CMHandlerAddr *metaDataHandler)
{
ContainerPtr container = baseValueHdr->container;
CM_CHAR *typeName;
MetaHandlerPtr metaHandler;
CMHandlerAddr useValueHandler;
SessionSuccess = true; /* assume success */
/* Check to see if the type object is for a global type name... */
if ((typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName)) == NULL)
return (NULL); /* no dynamic value for this type */
/* Check to see if the type name has an associated metahandler... */
metaHandler = cmLookupMetaHandler((CM_UCHAR *)typeName, container->sessionData);
if (!SessionSuccess) { /* if allocation error... */
ERROR1(CM_err_HandlerError, typeName); /* ...yell */
metaHandler = NULL;
}
/* If there is a metahandler, check to see if the metahandler has a "use value" */
/* handler... */
if (metaHandler == NULL || metaHandler->metaHandler == NULL)
return (NULL);
useValueHandler = (CMHandlerAddr)(*metaHandler->metaHandler)(NULL, (CMGlobalName)CMUseValueOpType);
if (useValueHandler == NULL) /* no "use value" handler ==> not dynamic */
return (NULL);
/* If the caller requested the "use value" handler, and we didn't find one, we assume */
/* the type is NOT for a dynamic value. If we have the "use value" handler and the */
/* new value handler is not needed, simply return the "use value" handler as the */
/* function result. However, if the "new value" handler is requested, and it's not */
/* there, we have an error. Note that if the "new value" handler is needed, then the */
/* "metadata" handler will be also. It's both or neither. The "metadata" handler is */
/* always required just the "new value" handler for CMNewValue(). */
if (newValueHandler == NULL) /* if "new value" handler not needed... */
return (useValueHandler); /* ...return "use value" handler */
*newValueHandler = (CMHandlerAddr)(*metaHandler->metaHandler)(NULL, (CMGlobalName)CMNewValueOpType);
if (*newValueHandler == NULL) { /* return "new value" handler if we got it*/
ERROR1(CM_err_NoNewValueHandler,typeName);/* ...yell again */
SessionSuccess = false;
return (NULL);
}
*metaDataHandler = (CMHandlerAddr)(*metaHandler->metaHandler)(NULL, (CMGlobalName)CMDefineMetaDataOpType);
if (*metaDataHandler == NULL) { /* return "metadata" handler if we got it */
ERROR1(CM_err_NoMetaDataHandler,typeName);/* ...yell again and again */
SessionSuccess = false;
return (NULL);
}
return (useValueHandler); /* tell cmFollowTypes() it's dynamic */
}
/*---------------------------------------------------------------------------------------*
| createDataPacket - allocate data packet for "new value" handler using type's metadata |
*---------------------------------------------------------------------------------------*
For a given type, its "metadata" (determined by the type's "metadata" handler) is used to
consume constructorData parameters (a CMNewValue() "..." parameter list). The parameters
are used to construct a data packet which will be passed to a "new value" handler.
The packet is allocated using the memory allocator handler. The caller (cmFollowTypes())
is expected to free the allocation. The pointer to the packet is returned as the
function result. The constructorData is updated to point at the first "..." parameter
not consumed (if any).
Note, a null byte is added to the end of the data packet to make life a little easier for
cmVScanDataPacket() if that is used to extract the data into variables.
NULL is returned if no packet is allocated because there is no metadata.
SessionSuccess, a session global status switch, is returned false if an allocation error
or inconsistency data check is reported. It is returned true otherwise.
*/
static CM_UCHAR *CM_NEAR createDataPacket(TOCObjectPtr type, CM_CHAR *metaData, va_list *constructorData)
{
ContainerPtr container = type->container;
CM_UCHAR c, *s, *dataPacket, *dp;
CM_CHAR *mp, m, *typeName, insert[10];
CM_ULONG packetSize, l;
CM_USHORT d;
CMSize n;
void *p;
va_list ap;
SessionSuccess = true; /* assume success */
/* If there is no metadata, return NULL to indicate there's no data packet... */
if (metaData == NULL || *metaData == '\0') return (NULL);
/* Prescan the metaData to see how large it is. We must keep in sync with the */
/* CMNewValue() "..." parameters because some of the metadata refer may refer to */
/* strings (%s and %*s). Thus the size is not a simple function of metadata length. */
packetSize = 0; /* sum packet size in here */
mp = metaData; /* mp will be metaData pointer */
ap = *constructorData; /* leave original constructorData ptr alone */
while ((CMBoolean)(m = *mp++)) { /* scan the metaData... */
if (m != Meta_Trigger) continue; /* if not format specification, loop */
switch ((CMBoolean)(m = *mp++)) {
case Meta_Value: /* %v */
case Meta_Object: /* %o */
case Meta_Ptr: p = va_arg(ap, void *); /* %p */
packetSize += sizeof(void *);
continue;
case Meta_Char: c = va_arg(ap, CM_UCHAR); /* %c */
packetSize += sizeof(CM_UCHAR);
continue;
case Meta_Short: d = va_arg(ap, CM_USHORT); /* %d */
packetSize += sizeof(CM_USHORT);
continue;
case Meta_Long: if (*mp == Meta_LongSuffix) ++mp; /* %l[d] */
case Meta_ID: l = va_arg(ap, CM_ULONG); /* %i */
packetSize += sizeof(CM_ULONG);
continue;
case Meta_Star: if ((m = *mp++) != Meta_String) { /* %*s */
insert[0] = '*';
insert[1] = m;
insert[2] = '\0';
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR3(CM_err_BadMetaSpec, insert, typeName, CONTAINERNAME);
return (NULL);
}
n = va_arg(ap, CMSize);
s = va_arg(ap, CM_UCHAR *);
packetSize += n + sizeof(CMSize);
continue;
case Meta_String: s = va_arg(ap, CM_UCHAR *); /* %s */
packetSize += strlen((CM_CHAR *)s) + 1;
continue;
default: insert[0] = m; /* oops! */
insert[1] = '\0';
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR3(CM_err_BadMetaSpec, insert, typeName, CONTAINERNAME);
SessionSuccess = false;
return (NULL);
} /* switch */
} /* while */
/* Now that we got the size, and we're happy with the metaData, allocate the packet...*/
dataPacket = (CM_UCHAR *)CMmalloc(packetSize + 1); /* we add a null to the end */
if (dataPacket == NULL) {
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR2(CM_err_NoDataPacket, typeName, CONTAINERNAME);
SessionSuccess = false;
return (NULL);
}
/* Rescan the metadata and put the data into the newly allocated data packet. Note, */
/* data alignment is not assumed here. We use memmove() to build the packet. */
mp = metaData; /* mp will be metaData pointer again */
dp = dataPacket; /* enter each data item at dp */
ap = *constructorData; /* set to consume parameters again */
while ((CMBoolean)(m = *mp++)) { /* rescan the metaData... */
if (m != Meta_Trigger) continue; /* if not format specification, loop */
switch ((CMBoolean)(m = *mp++)) {
case Meta_Value: /* %v */
case Meta_Object: /* %o */
case Meta_Ptr: p = va_arg(ap, void *); /* %p */
memmove(dp, &p, sizeof(void *));
dp += sizeof(void *);
continue;
case Meta_Char: c = va_arg(ap, CM_UCHAR); /* %c */
memmove(dp, &c, sizeof(CM_UCHAR));
dp += sizeof(CM_UCHAR);
continue;
case Meta_Short: d = va_arg(ap, CM_USHORT); /* %d */
memmove(dp, &d, sizeof(CM_USHORT));
dp += sizeof(CM_USHORT);
continue;
case Meta_Long: if (*mp == Meta_LongSuffix) ++mp; /* %l[d] */
case Meta_ID: l = va_arg(ap, CM_ULONG); /* %i */
memmove(dp, &l, sizeof(CM_ULONG));
dp += sizeof(CM_ULONG);
continue;
case Meta_Star: ++mp; /* %*s */
n = va_arg(ap, int);
s = va_arg(ap, CM_UCHAR *);
memmove(dp, &n, sizeof(CMSize)); /* (prefix with "*") */
dp += sizeof(CMSize);
memmove(dp, s, (size_t)n);
dp += n;
continue;
case Meta_String: s = va_arg(ap, CM_UCHAR *); /* %s */
strcpy((CM_CHAR *)dp, (CM_CHAR *)s);
dp += strlen((CM_CHAR *)s) + 1;
continue;
} /* switch */
} /* while */
*dp = (CM_UCHAR)0; /* add null byte for cmVScanDataPacket() */
*constructorData = ap; /* point constructorData at next set of args */
return (dataPacket); /* return data packet to the caller */
}
/*---------------------------------------------------------*
| newDynamicValue - create a new "dynamic" value (header) |
*---------------------------------------------------------*
This routine is called only by cmFollowTypes() to do the actual construction of the
dynamic value for the specified base value. The type is the type that is causing this
dynamic value to be created. If the base value is a "real" value, then the dynamic value
is added to a "dynamic values" property for the object who owns the value. The "dynamic
value"s property is created for the first dynamic value for that object. All further
dynamic values with "real" value bases are simply added as values (headers) to that
property. If the base value is itself a dynamic value, then the newly created dynamic
value is chained to the base (dynamic) value. It is only a backward link from the new
dynamic value.
The pointer to the new dynamic value is returned. NULL is returned if any errors are
reported. Note, the newOrUseValueName parameter is the string "CMNewValue" or
"CMUseValue" and only used for some of the error inserts.
By passing the returned dynamic value pointer as the base value to succeeding
newDynamicValue() calls, layers of dynamic values are constructed.
See the dynamic value documentation at the start of this file for details on the data
structures.
*/
static TOCValueHdrPtr CM_NEAR newDynamicValue(TOCValueHdrPtr baseValueHdr,
TOCObjectPtr type,
CMMetaHandler metaHandler,
CMRefCon refCon,
CM_CHAR *newOrUseValueName)
{
ContainerPtr container = baseValueHdr->container;
TOCObjectPtr theObject;
TOCValueHdrPtr theDynamicValueHdr, theRealValueHdr;
DynValueHdrExtPtr extensions;
CMBoolean firstDynamicValue;
CM_CHAR *typeName;
if (metaHandler == NULL) { /* we must have a metahandler! */
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR2(CM_err_NoMetahandler, typeName, CONTAINERNAME);
return (NULL);
}
/* It is an error for layered dynamic values to pass as the base value any other */
/* value header other than the most recent. The code near the end of this routine */
/* chains layered dynamic values together with each value pointing back to its base. */
/* The original "real" value gets a pointer to the last one (in the dynValue union */
/* field of the value header). So by searching back up the chain to the "real" value */
/* we can get at this pointer to see if the base value passed is equal to it and */
/* report an error if it is not. Of course, the first time the "real" value is passed */
/* and the dynValue will be NULL. So we will know this case too. */
theRealValueHdr = baseValueHdr; /* start with the current base */
while (IsDynamicValue(theRealValueHdr)) /* loop back till we find 1st value */
theRealValueHdr = DYNEXTENSIONS(theRealValueHdr)->baseValue;/*that not dynamic value*/
firstDynamicValue = (CMBoolean)(theRealValueHdr->dynValueData.dynValue == NULL);
if (!firstDynamicValue && /* do the base check... */
theRealValueHdr->dynValueData.dynValue != baseValueHdr) {
ERROR2(CM_err_BadRealValue, newOrUseValueName, CONTAINERNAME); /* ...oops! */
return (NULL);
}
/* Create the dynamic value, i.e., a value (header) that is belongs to a special */
/* property of the caller's object that owns the base value. The object "owning" the */
/* dynamic value is flagged to indicate there are dynamic values present in that */
/* object. This protects it from premature deletion. The flag is cleared when the */
/* last dynamic value for for the object is released. We don't use the "protected" */
/* flag, which is used for similar purposes, just to be general. Maybe we could */
/* someday have a protected object with dynamic values! */
/* Note, the type we give the dynamic value is that of the type that caused this */
/* dynamic value to be generated. We never use that fact. But we got to give it */
/* something and it could aid in debugging this turkey! Do you really think I am */
/* going to get all this right the first time? Want to buy a bridge? */
theObject = cmDefineObject(container, baseValueHdr->theProperty->theObject->objectID,
CM_StdObjID_DynamicValues,
0, /* 0 stops dup checks, was "type->objectID" */
NULL, container->generation, kCMDynValue,
ObjectObject, &theDynamicValueHdr);
if (theObject == NULL) return (NULL);
theDynamicValueHdr->typeID = type->objectID; /* give the type ID something valid */
theObject->objectFlags |= DynamicValuesObject; /* make sure object is flagged */
/* The dynValueData field in the value header is a union with two alternatives. The */
/* "dynValue" alternative is a pointer from the base value header to its dynamic */
/* value. The "extensions" alternative is for the dynamic value header itself and */
/* points to the additional information, i.e., extensions, needed to accompany a */
/* dynamic value header. A cmDefineObject() call always initializes the union field */
/* to NULL on the assumption a "normal" value header is being created. Only in here */
/* do we create dynamic value headers. Thus only in here do we fill in these fields. */
/* We just got done creating the dynamic value header. So the first thing to do is */
/* to allocate its extensions data. So here goes... */
if ((extensions = (DynValueHdrExtPtr)CMmalloc(sizeof(DynValueHdrExt))) == NULL) {
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR2(CM_err_NoDynExtensions, typeName, CONTAINERNAME);
return (NULL);
}
/* Point the extensions union alternative at the extensions space we just alloacted. */
/* Also, while we're screwing around in the dynamic value save the refCon passed in. */
/* The valueRefCon field is convenient so that CMGetValueRefCon() will be able to be */
/* used by the dynamic value handlers just as it would with normal values. */
DYNEXTENSIONS(theDynamicValueHdr) = extensions; /* point at the extensions data */
theDynamicValueHdr->valueRefCon = refCon; /* remember the refCon */
theDynamicValueHdr->useCount = 1; /* set use count to 1st use */
/* We now must init the extensions fields. Among the fields are the dynamic value */
/* handler pointer vector (our "v-table"). It consists of the pointers to the */
/* corresponding value operation handler, a "protect-from-recursive-use" flag, and */
/* the value "this" pointer. We init all this stuff to NULL and "not called". The */
/* metahandler passed to this routine will be used to fill in the handler pointers on */
/* first use (see cmGetDynHandlerAddress()). It will set the "this" pointer too. */
/* To make it easier to init in the vector, the following macro is used: */
#define InitDynVector(h) extensions->dynValueVector.h.handler = NULL; \
extensions->dynValueVector.h.thisValue = NULL; \
extensions->dynValueVector.h.active = false;
InitDynVector(cmGetValueSize); /* prepare handler vector... */
InitDynVector(cmReadValueData);
InitDynVector(cmWriteValueData);
InitDynVector(cmInsertValueData);
InitDynVector(cmDeleteValueData);
InitDynVector(cmGetValueInfo);
InitDynVector(cmSetValueType);
InitDynVector(cmSetValueGen);
InitDynVector(cmReleaseValue);
extensions->metaHandler = metaHandler; /* remember metahandler address */
extensions->baseValue = baseValueHdr; /* point dyn value hdr to its base */
/* For layering dynamic values, a backward chain of dynamic value headers is formed */
/* from the new dynamic value header back to the original "real" value header. The */
/* link is the baseValue extensions field set above. It always points to its base */
/* value (it is also used by CMGetBaseValue()). As mentioned above, the dynValue */
/* union alternative in a "real" value header always points to the last dynamic */
/* value of the chain. A CMUseValue() or CMNewValue() will always return the pointer */
/* to the dynamic value at the end of the chain. So, we always want to set the */
/* dynValue pointer to the new dynamic value header we created above. Note also we */
/* use this pointer to make sure the value passed as a base to us here is indeed the */
/* pointer to the end of the chain. We did this check near the start of this routine.*/
/* From that check we get the pointer to the original "real" value. So now we can */
/* set that pointer to the new end of chain. */
theRealValueHdr->dynValueData.dynValue = theDynamicValueHdr;
/* Layered dynamic values, i.e., dynamic values that have a dynamic value base are */
/* NOT treated as distinct values for special property whose chain we put them on when*/
/* we did the cmDefineObject() above. This means we must take such a value OFF the */
/* special property chain. It's not getting lost though. Remember we just got done */
/* pointing the "real" value dynValue at it. Note, we flag this value header as */
/* being off the chain. This makes it a simple test later when we want to delete */
/* these puppies off the layering chain (done by CMReleaseValue()). */
if (!firstDynamicValue) { /* do only if not the first time */
cmDeleteListCell(&theDynamicValueHdr->theProperty->valueHdrList, theDynamicValueHdr);
cmNullListLinks(theDynamicValueHdr);
theDynamicValueHdr->valueFlags |= ValueOffPropChain; /* mark as off the prop. chain */
} else /* if we have a NEW dynamic value...*/
++container->targetContainer->nbrOfDynValues; /* ...keep count in targetContainer */
return (theDynamicValueHdr); /* give caller the dynamic value */
}
/*----------------------------------------------------------------------------------*
| cmDeleteAllDynamicValueLayers - free memory for a single dynamic value layer set |
*----------------------------------------------------------------------------------*
This routine is used in two contexts; first, for error recovery to free all the memory
for a newly allocated dynamic value layer set, and second, when freeing the entire TOC
to make usre all memory for any "dangling" dynamic values are freed.
The current top-most dynamic value layer is passed in theValueHdr. The layers are
back-linked up to the base "real" value. All the dynamic value memory on this chain are
freed. If freeValueHdr is true, then we were creating dynamic values for CMNewValue().
In that case, the real value itself is freed. Really freed! Not marked free as is the
usual case.
The cmMarkValueDeleted() is called to free the real value. Its parameters are such that
the value header memory will be freed rather than being put on its deleted values list.
That routine will also delete the property if the value is the only one for its property.
*/
void cmDeleteAllDynamicValueLayers(TOCValueHdrPtr theValueHdr, CMBoolean freeValueHdr)
{
TOCValueHdrPtr nextLayer;
TOCPropertyPtr theProperty;
ContainerPtr container = theValueHdr->container;
if (IsDynamicValue(theValueHdr)) {
theProperty = theValueHdr->theProperty; /* clear obj flag if last dyn valu*/
if (cmCountListCells(&theProperty->valueHdrList) == 1)
theProperty->theObject->objectFlags &= ~DynamicValuesObject;
if (container->targetContainer->nbrOfDynValues>0) /* adjust total dyn value count */
--container->targetContainer->nbrOfDynValues;
while (IsDynamicValue(theValueHdr)) { /* free all these dyn values... */
nextLayer=DYNEXTENSIONS(theValueHdr)->baseValue;/* ...remember the next layer up */
if ((theValueHdr->valueFlags & ValueOffPropChain) == 0) /* ...del. from property */
cmDeleteListCell(&theProperty->valueHdrList, theValueHdr); /*(bottom layer only)*/
CMfree(DYNEXTENSIONS(theValueHdr)); /* ...free the extensions... */
CMfree(theValueHdr); /* ...and the header itself */
theValueHdr = nextLayer; /* loop up to the "real" value */
} /* while */
}
if (freeValueHdr) /* if CMNewValue() or freeing TOC */
cmMarkValueDeleted(theValueHdr->container, theValueHdr, true);/* free new real value*/
}
/*-------------------------------------------------------------*
| cmFollowTypes - follow a type through all of its base types |
*-------------------------------------------------------------*
This routine creates a dynamic value layers for the passed type and all of its base types,
if any of these types have a "use value" handler. This routine is only called by
CMNewValue() or CMUseValue(). For CMNewValue(), "metadata" and "new value" handlers are
also required. The top-most dynamic value header pointer is returned. This should be
returned from CMUseValue() or CMNewValue(). NULL is returned if an error is reported.
The original "real" value is returned if no dynamic values are created.
For CMUseValue(), isNewValue should be set to false. It should only be set to true for
CMNewValue(). Also for CMNewValue(), the constructorData must point at the CMNewValue()
"..." parameters. These are consumed as the base type metadata (returned from the
"metadata" handler) describes how to create data packets from the "..." parameters. The
packets, in turn, are passed to the "new value" handlers. A "new value" handler uses its
data packet to write (possibly different) data to its base value. This written data will
then be read and used by the "use value" handler.
The "use value" handler is called for both the CMUseValue() and CMNewValue() cases. If
it's companion "new value" handler wrote data to its base value, the "use value" handler
will probably read the data to create its refCon. The refCon will be passed to all
value handlers. The "use value" handler returns its refCon along with another
metahandler address that is used to get the value handler addresses. These are then
used to create the dynamic value.
See dynamic value documentation at the start of this file for a description of the
prototypes for the "metadata", "new value", and "use value" handlers, as well as the
description of the metadata itself.
To produce all the required dynamic values, cmFollowTypes() recursively follows the types,
looking for base types as defined by CMAddBaseType(). Each type can have any number of
base types. The recursion effectively produces a depth-first search of all the base
types.
As each type is completed (i.e., no more base types for it), a dynamic value is created
as described above. That is, for CMNewValue(), a type's "metadata" handler instructs us
on how many CMNewValue() "..." parameters to consume and how to construct their packet.
That is passed to the "new value" handler so it can write some appropriate data to the
base value. The "use value" is called in all cases which reads the data written by "new
value" to construct its refCon. The refCon is returned here along with the metahandler
address that will yield the value handler routine addresses.
The refCon and metahandler address are passed to newDynamicValue() to construct one
dynamic value (layer). The resulting dynamic value is used as the base value for the
next layer. This produces the desired data structures.
Note, because this routine searches through the types down to their leaves, and then
generates the dynamic values on the way back "up", the CMNewValue() "..." parameters
must be ordered for the "deepest" type first. For example, given the following type
inheritance heirarchy (read these as T1 inherits from T2 and T3, and so on):
T4 T5
* *
* *
T2 T3
* *
* *
T1
The depth-first search, starting at T1, yields the sequence: T2 T4 T5 T3 T1. Then this
is the order the CMNewValue() "..." parameters must be in. It is also the order the
dynamic value layers are generated. T1 is the top layer, and T2 the bottom. T1 would be
the dynamic value returned from CMUseValue() or CMNewValue().
*/
TOCValueHdrPtr cmFollowTypes(TOCValueHdrPtr baseValueHdr, TOCObjectPtr type,
CMBoolean isNewValue, va_list *constructorData)
{
ContainerPtr container = baseValueHdr->container;
TOCObjectPtr baseType;
TOCPropertyPtr baseTypeProperty;
TOCValueHdrPtr theValueHdr, baseValueHdr0;
TOCValuePtr theValue;
CMHandlerAddr useValueHandler, newValueHandler, metaDataHandler;
CM_UCHAR *dataPacket;
CM_CHAR *newOrUseValueName, *metaData, *typeName;
CMRefCon refCon;
CMMetaHandler metaHandler;
CMBoolean success;
/* If any errors are reported, and the error handler returns, we NULL out the base */
/* value. That essentialy "puts the breaks" on the dynamic value generation. The */
/* NULL result will work ist way back to CMNewValue() or CMUseValue() which it will */
/* return. */
/* Note, as soon as we detect an error in here, we free all the dynamic values we */
/* created for the "real" value. That should put things back the way they were */
/* originally. Of course, error reporters are not suppose to return. We're being */
/* "kind" here just in case they do. */
if (baseValueHdr == NULL) return (NULL);
baseValueHdr0 = baseValueHdr; /* save current baseValueHdr for errors */
/* If the current type has any base types, call cmFollowTypes() recursively for each */
/* of those base types. That brings us back to here where we will check each of */
/* those base types for base types of their own. This process continues down to the */
/* "bottom", i.e., leaf types, where we bypass this piece of code. The types are then */
/* processed as the recursion unwinds. It is this code, then the effects the depth- */
/* first processing of the base types. */
baseTypeProperty = cmGetObjectProperty(type, CM_StdObjID_BaseTypes);
if (baseTypeProperty != NULL) { /* process this type's base types */
theValueHdr = (TOCValueHdrPtr)cmGetListHead(&baseTypeProperty->valueHdrList);
if (theValueHdr != NULL) { /* double check for value hdr */
theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList); /* 1st base ID */
while (theValue != NULL) {
baseType = cmFindObject(container->toc, theValue->value.imm.ulongValue);
if (baseType) {
baseValueHdr = cmFollowTypes(baseValueHdr, baseType, isNewValue, constructorData);
if (baseValueHdr == NULL) return (NULL);
}
theValue = (TOCValuePtr)cmGetNextListCell(theValue); /* look at next base type */
} /* while */
} /* theValueHdr */
} /* baseTypeProperty */
/* We are now at a type which has either has no base types or that had all of its base*/
/* types processed (again through here). This is where we determine whether the type */
/* will spawn a dynamic value. We call hasUseValueHandler() that checks for the */
/* the "use value" handler for the type. It also checks for the "metadata" and "new */
/* value" handlers if we were originally called from CMNewValue(). If it doesn't pass*/
/* the tests we return the original base value unchanged. If it does pass the tests, */
/* we create a new dynamic value (layer). */
/* As descibed in the documentation at front of this file, we call the "metadata" and */
/* "new value" handlers first only for CMNewValue(). For CMNewValue() or CMUseValue() */
/* we then call the "use value handler. For the "new value" call we must first build */
/* the data packet from the CMNewValue() "..." parameters using the metadata returned */
/* from the "metadata" handler. The packet is sent to the "new value" handler. There */
/* it can use CMScanDataPacket() to extract the packet back into variables. */
/* Note, as the packets for the layers are created, the constructorData parameter */
/* pointer marches across the CMNewValue() "..." parameters. Since we are building */
/* the layers as we move up the inheritance hierarchy, from bottom to top, that is */
/* what determines the parameter specifications discussed above. Further, it is also */
/* why the constructorData is a va_list*. The higher layers must know what the */
/* current parameter pointer is. If we had built the hierarchy from top to bottom we */
/* wouldn't have needed the "*". */
/* And speaking of layers -- the newDynamicValue() call is the one responsible for */
/* creating a layer. It builds upon the current baseValueHdr and returns a new one. */
/* they are passed up the recursive calls as the function result to build the layer */
/* chain. The final, top-most layer, is the one eventually returned to CMNewValue() */
/* or CMUseValue(), who then returns it to the user. */
useValueHandler = hasUseValueHandler(baseValueHdr, type, isNewValue ? &newValueHandler : NULL, &metaDataHandler);
if (!SessionSuccess) return (NULL);
if (useValueHandler) { /* if dynamic value layer is to be created*/
if (isNewValue) { /* extra stuff for CMNewValue()... */
/* Here we're doing a CMNewValue() -- call the "metadata" handler to get this */
/* type's metadata. We can then use that to build a data packet. */
metaData = (*(CM_CHAR *(*)(CMType))metaDataHandler)((CMType)type);
dataPacket = createDataPacket(type,metaData,constructorData); /*create data pkt...*/
if (!SessionSuccess) { /* ...if it failed... */
cmDeleteAllDynamicValueLayers(baseValueHdr0,isNewValue);/* ...delete all layers */
return (NULL); /* ...abort creation */
}
/* Call the "new value" handler. It should use the data packet to write data to */
/* its base value. */
success = (*(CMBoolean (*)(CMValue, CMType, CMDataPacket))newValueHandler)((CMValue)baseValueHdr, (CMType)type, (CMDataPacket)dataPacket);
if (dataPacket != NULL) CMfree(dataPacket); /* free the packet */
newOrUseValueName = "CMNewValue";
} else {
newOrUseValueName = "CMUseValue";
success = true;
}
/* In all cases we call the "use value" handler. It should read the data written by */
/* the "new value" handler to create its refCon. It returns the refCon and a */
/* methandler pointer that we will use to get the value operation handler addresses.*/
/* This is then passed to newDynamicValue() to build the new dynamic value (layer). */
if (success) {
metaHandler = NULL; /* "use value" must set this or error */
success = (*(CMBoolean (*)(CMValue, CMType, CMMetaHandler*, CMRefCon*))useValueHandler)((CMValue)baseValueHdr, (CMType)type, &metaHandler, &refCon);
if (success) {
if (metaHandler == NULL) { /* we must get a methandler back! */
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR2(CM_err_NoDynMetahandler, typeName, CONTAINERNAME);
success = false;
} else
baseValueHdr = newDynamicValue(baseValueHdr, type, metaHandler, refCon, newOrUseValueName);
}
}
if (!success || baseValueHdr == NULL) { /* if something went wrong... */
cmDeleteAllDynamicValueLayers(baseValueHdr0, isNewValue); /* ...free the layers */
baseValueHdr = NULL; /* this will abort the recursion */
}
}
return (baseValueHdr); /* return dynamic value layer */
}
/*--------------------------------------------------------------*
| cmGetDynHandlerAddress - get a dynamic value handler address |
*--------------------------------------------------------------*
A dynamic value handler is callable if it exists (of course!) and it is not being used
recursively. The vectorEntry points to the dynamic value entry in its vector belonging
to the extensions of the passed dynamic value. If this is not the first use of the
handler, the vector entry contains the handler address and its associated ("this") value
(discussed below). If it is first use, cmGetDynHandlerAddress() must find the
("inherited") handler address and its associated "this" value.
No matter which case it is, the "this" value is returned as the function result. NULL
is returned if an error is reported (discussed later).
The returned value, and the one saved in the vector entry may not be the same. They are
if the passed value has a handler. If it doesn't, an "inherited" handler, from one of
the dynamic value's base values is used. The value associated for whoever has the handler
is the "owning" value. In C++ terms, it is the "this" pointer.
In the limit, we could end up using the original "real" value that spawned the dynamic
value(s). In that is indeed the case, we end up using the calling routine which will
always be an API value operation (see DynValus.h to see how we use the macros that
call this routine).
The found handler address and "this" pointer are saved in the passed dynamic value's
vector entry so we don't have to do the search on successive uses. The "this" pointer
and handler address are also saved in the vector entry corresponding to "this" (unless,
of course, it is the "real" value).
In the vector entry there is also a boolean flag that tells us whether the handler is
currently active, i.e., in the call chain. If it is we have a recursion attempt. This
causes an error report and NULL to be returned. Since we will always find the handler
or use the "real" value, we can never get an error from that. The recursion is the only
error condition.
Caution -- Implementation/portability Note
In order to get at the corresponding dynamic vector in the vector of the final "this"
value, we convert the original vectorEntry to an offset in the value header extensions,
i.e., offset to the vector entry within a DynValueHdrExt struct. This is used to
"relocate" to the corresponding vector entry. I point this out only to make you aware of
this "trick" (s it really a trick?). It appears to be standard ANSI C because fields in
a struct are supposed to be ordered by ascending address. In other words it is NOT
implementation defined how to lay out fields in a struct (except for alignment). If your
compiler thinks otherwise, this will be a problem area. I just thought you would like to
know!
Oh, by the way, the code we're talking about here is near the end of the routine.
*/
CMValue cmGetDynHandlerAddress(CMValue value, DynamicValueVectorEntriesPtr vectorEntry,
CMconst_CMGlobalName operationType, CM_CHAR *routineName)
{
CM_LONG entryOffset;
CMValue origValue;
ContainerPtr container;
CMHandlerAddr handler;
/* It's an error to attempt a recursive call... */
if (vectorEntry->active) { /* error if recursing... */
container = ((TOCValueHdrPtr)value)->container;
ERROR2(CM_err_HandlerRecursed, routineName, CONTAINERNAME);
return (NULL);
}
/* We are not recursing and this is not the first time we're using this handler, just */
/* return the "this" pointer associated with the handler we found on an earlier call. */
/* They were initialized to NULL when the vector was allocated. We cannot test the */
/* handler pointer because it will not be set if the "this" pointer is for a base */
/* "real" value. */
if (vectorEntry->thisValue != NULL) return (vectorEntry->thisValue);
/* This is the first call to the handler. We now must search up the dynamic value */
/* chain, starting with the passed dynamic value, looking for the first dynamic value */
/* that supplies the handler. The handler is accessed in the usual way via its dynamic*/
/* value metahandler. */
origValue = value; /* remember starting dynamic value */
for (;;) { /* loop till we find the handler... */
handler = (CMHandlerAddr)(*DYNEXTENSIONS(value)->metaHandler)(NULL, operationType);
if (handler != NULL) break; /* break when we find the handler */
/* If not found, we must get the current value's base value. We can do this until */
/* we reach the original "real" value. If we do reach it, then that stops the search*/
/* and it is the "real" value we return. In that case, there is no handler because */
/* the calling API value routine will directly operate on the value. Thus we leave */
/* the handler address in the original value alone. Of course we set the "this" */
/* pointer. The next time the dynamic value is used, we will return the "real" */
/* value. As discussed in DynValus.h , the caller tests the returned value */
/* to see if it is still a dynamic value. If it isn't, the handler use is bypassed.*/
value = (CMValue)(DYNEXTENSIONS(value)->baseValue);/* basically a CMGetBaseValue() */
if (!IsDynamicValue(value)) { /* if we reached the "real" value...*/
vectorEntry->thisValue = value; /* save "this" value for next time */
return (value); /* return the "real" value as "this"*/
}
} /* for */
/* Set the "this" pointer and handler address in the original vector entry for the */
/* next time we use it. Note that we can only exit the above "for" loop if we find */
/* the handler. If we loop up to the "real" value without finding it, we exit the */
/* routine from inside the loop after saving the "this" pointer as we do here. */
vectorEntry->thisValue = value; /* remember "this" */
vectorEntry->handler = handler; /* remember its handler address */
/* Just to be safe, we want to copy the "this" pointer and handler address in the */
/* vector entry corresponding to "this". Of course we only need to do this if we */
/* "inherited" a handler (i.e., we looped up to a base dynamic (but not real) value. */
/* Note, this is where we do the struct offset relocation warned about earlier. */
if (value != origValue) { /* if "inherited"... */
entryOffset = (CM_CHAR *)vectorEntry - (CM_CHAR *)DYNEXTENSIONS(origValue); /* relocate...*/
vectorEntry = (DynamicValueVectorEntriesPtr)((CM_CHAR *)DYNEXTENSIONS(value) + entryOffset);
vectorEntry->thisValue = value; /* set "this"s vector entry */
vectorEntry->handler = handler;
}
return (value); /* return "this" */
}
/*-------------------------------------------------------------*
| cmVScanDataPacketGuts - extract the data from a data packet |
*-------------------------------------------------------------*
This routine is the "guts" of the routine CMScanDataPacket(). It is used to extract the
fields of a data packet created from CMNewValue() "..." parameters according to the
specified metadata and place it in the "dataInitParams" parameters. Each dataInitParams
parameter must be a pointer; extracted data read from the data packet are stored into the
locations pointed to by the pointers.
The Container Manager accesses the metadata through a "metadata" handler for the type to
build the data packet. cmVScanDataPacket() inverts the operation and allows a "new
value" handler to extract the data back into distinct variables. The "new value" handler
can use its own "metadata" handler to pass to the CMScanDataPacket() routine to extract
the data.
The function returns the number of data items extracted and assigned to the parameters.
This could be 0 if the type does not have any metadata (or if an error is reported).
NULL may be passed as the dataPacket or metaData pointer to indicate there is no
metadata.
The metadata format is discussed in the documentation at the start of this file. The only
special case is for the "%*s" format specification. This generates a fixed length string
(not null terminated) of n characters, where n is determined from a parameter in the
CMNewValue() "..." paramters. The value of n is explicitly written to the packet data in
front of the string. For symmetry (and so we can determine the number of characters to
extract from the packet data), we return the value of n to an explict parameter pointer
passed here. Thus the parameter pointer list passed to this routine should be identical
to the "..." parameters passed to a CMNewValue() "..." parameter list (at least the
portion corresponding to this type).
Here's a word of caution about "%s" -- watch it! As discussed in note 4 of the format
specification comments, we COPY the string from the packet to the place pointed to by the
corresponding dataInitParams parameter. This is a pitfall (I fell into it, hence these
comments). C strings are thought of as pointers (at least I think of them that way). So there is a
tendency to use "%s" like you would in "printf()". WRONG! What you really want is a
pointer, so use "%p"!
*/
CM_ULONG cmVScanDataPacketGuts(TOCObjectPtr type, CM_CHAR *metaData,
CM_UCHAR *dataPacket, va_list dataInitParams)
{
ContainerPtr container = type->container;
CM_UCHAR *dp, *p;
CM_CHAR *mp, m, *typeName, insert[10];
CM_ULONG nbrAssigned;
CMSize n;
/* If there is not data packet or not metaData, there is nothing to do... */
if (dataPacket == NULL || metaData == NULL || *metaData == '\0')
return (0);
/* Scan the metaData and extract the dataPacket fields according to the that metadata.*/
/* Each field is put into the location pointed to by the next dataInitParams */
/* parameter. */
nbrAssigned = 0; /* count nbr of parameters assigned */
mp = metaData; /* mp will be encoding pointer */
dp = dataPacket; /* each data packet item starts at dp */
while ((CMBoolean)(m = *mp++)) { /* scan the encoding... */
if (m != Meta_Trigger) continue; /* if not format specification, loop */
p = va_arg(dataInitParams, CM_UCHAR *); /* point at next parameter */
switch ((CMBoolean)(m = *mp++)) {
case Meta_Value: /* %v */
case Meta_Object: /* %o */
case Meta_Ptr: memmove(p, dp, sizeof(void *)); /* %p */
dp += sizeof(void *);
++nbrAssigned;
continue;
case Meta_Char: memmove(p, dp, sizeof(CM_UCHAR)); /* %c */
dp += sizeof(CM_UCHAR);
++nbrAssigned;
break;
case Meta_Short: memmove(p, dp, sizeof(CM_USHORT)); /* %d */
dp += sizeof(CM_USHORT);
++nbrAssigned;
continue;
case Meta_Long: if (*mp == Meta_LongSuffix) ++mp; /* %l[d] */
case Meta_ID: memmove(p, dp, sizeof(CM_ULONG)); /* %i */
dp += sizeof(CM_ULONG);
++nbrAssigned;
continue;
case Meta_Star: if ((m = *mp++) != Meta_String) { /* %*s */
insert[0] = '*';
insert[1] = m;
insert[2] = '\0';
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR3(CM_err_BadMetaSpec, insert, typeName, CONTAINERNAME);
return (0);
}
memmove(&n, dp, sizeof(CMSize));
memmove(p, dp, sizeof(CMSize));
dp += sizeof(CMSize);
++nbrAssigned;
p = va_arg(dataInitParams, CM_UCHAR *);
memmove(p, dp, (size_t)n);
dp += n;
++nbrAssigned;
continue;
case Meta_String: strcpy((CM_CHAR *)p, (CM_CHAR *)dp); /* %s */
++nbrAssigned; /* see caution comments!*/
continue;
default: insert[0] = m; /* oops! */
insert[1] = '\0';
typeName = cmIsGlobalNameObject(type, CM_StdObjID_GlobalTypeName);
ERROR3(CM_err_BadMetaSpec, insert, typeName, CONTAINERNAME);
return (0);
} /* switch */
} /* while */
return (nbrAssigned);
}
CM_END_CFUNCTIONS