ObjectStore Java API User Guide
Chapter 8
Automatically Generating Persistence-Capable Classes
This chapter provides information and instructions for using the class file postprocessor to make classes persistence-capable. Reference information for all postprocessor options is in Chapter 13, Tools Reference.
Caution
For simple applications, it is best to postprocess all classes together. For more complex applications, you can postprocess your classes in correctly grouped batches. See Postprocessing a Batch of Files Is Important.
Failure to postprocess the correct classes together can result in problem situations that appear when you try to run the application and that are hard to diagnose. There are postprocessor options that allow you to determine which classes are made persistence-capable.
This chapter discusses the following topics:
Overview of the Class File Postprocessor
Running the Postprocessor
Managing Annotated Class Files
Creating Persistence-Aware Classes
How the Postprocessor Works
Including Transient and Already Annotated Classes
Putting Processed Classes in a New Package
Creating Persistence-Capable Classes with Transient Fields
Customizing Updated Classes
Optimizing Operations That Retrieve Persistent Objects
Specifying the Number of Array Dimensions in Persistence-Capable Classes
Performing a Test Run of the Postprocessor
Using an Input File
Annotations You Must Add
Class File Postprocessor Limitations
Overview of the Class File Postprocessor
To store an object in a database, the object must be persistence-capable. For an object to be persistence-capable, it must include code that allows persistence. ObjectStore includes the class file postprocessor to automatically insert the required code, referred to as annotations, into your class files.
The command you use to run the class file postprocessor is osjcfp. The postprocessor provides a number of command options that allow you to tailor the results to your needs.
You can run the postprocessor on classes or class libraries that you create or that you purchase from a vendor. See COM\odi\demo\collections\README.htm for an example of making a third-party library persistence-capable.
You must explicitly postprocess or manually annotate each class that you want to be persistence-capable. The capacity for an object to be stored in a database is not inherited when you subclass a persistence-capable class.
When you postprocess or manually annotate a class, this registers the class with ObjectStore. If a class is not postprocessed or manually annotated, ObjectStore throws ClassNotRegisteredException.
This overview provides the following information:
Description of the Annotations
The class file postprocessor annotates classes you define so that they are persistence-capable. This means that the postprocessor makes a copy of your class files, places them in a directory you specify either the source directory or another directory), and adds byte-code instructions (annotations) that are required for persistence. These annotations are
Description of the Process
Before you run the postprocessor, you must compile your source files. The set of files you run the postprocessor on can contain a combination of class files, .zip files, and .jar files. The postprocessor generates annotated class files and places them in a directory that you specify.
This destination directory is never the original directory, unless you specify the -inplace option. When you are in a development cycle, it is best to specify a directory other than the original directory. Doing so avoids errors and provides both a persistence-capable and a transient version of the same class.
It is not necessary to recompile all classes before iteratively running the postprocessor. The requirement is that the compiled classes be consistent.
The postprocessor tries to minimize the amount of work it does. It checks file modification times and only reprocesses those files that have changed.
Postprocessing a Batch of Files Is Important
In one execution of the postprocessor, the postprocessor must operate on a correctly grouped set of files. For example, an application might use a file, perhaps a library, that is already annotated. You must not specify the annotated files when you run the postprocessor on the rest of the files in your application. Hence, the term batch means all files that the postprocessor must annotate in one execution of the osjcfp command. Each batch must have its own postprocessor destination directory for this to work correctly.
You can use the postprocessor -inplace option to create multiple batches. When you do, there is no requirement for the separate batches to be stored in different directories.
Example of one batch
When you write a program that uses persistence, the program usually consists of a batch (a set) of classes, for example, classes A, B, and C. They are typically defined in files called A.java, B.java, and C.java. It is possible for each class to reference the other classes. For example, B might refer to C, and C might refer to B. There is no ordering or layering; there are no rules for references among the classes.
When this is the scenario, you must run the postprocessor on all of these classes at the same time. You cannot run the postprocessor on each file individually. This is because when the postprocessor operates on A, it might refer to B and C. The postprocessor must have information about B and C to correctly annotate A.
Example of two batches
In relatively simple programs, there is only one batch involved. But sometimes there might be more than one batch in an application. Suppose, for example, that you want to write a persistent program that uses an existing library. An example of this is djgl, which is Object Design's persistence-capable version of ObjectSpace's JGL library. Your program consists of A, B, and C plus the JGL library.
Now, in a simple (one-batch) program, when you run the postprocessor, you always specify all files in your application. In this case, you do not want the postprocessor to operate on JGL because it has already been postprocessed. In fact, you probably do not have the class files that have not been postprocessed.
It is correct to run the postprocessor on only A, B, and C. This is because there is a rule: JGL classes never know about A, B, and C. After all, JGL was written, finished, and put on the shelf before A, B, and C were created.
There are two batches here:
Whenever you run the postprocessor, you must run it on a whole batch. Each batch must have its own postprocessor destination directory.
Checking for correct batches
To determine if you have correctly grouped your files in batches, you can apply this rule: Class A and class B must be in the same batch if either of the following is true:
Manual Annotation
In exceptional situations, you might want to insert all required annotations needed for persistence and not use the postprocessor at all. See Chapter 9, Manually Generating Persistence-Capable Classes. You can also manually annotate your code to meet some persistence requirements and then run the postprocessor to insert the other annotations.
Running the Postprocessor
To make classes persistence-capable, do the following:
- Compile the source files.
- Run the postprocessor on the resulting class files.
You must run the postprocessor on all class files in a batch at the same time.
Some Java-supplied classes are persistence-capable. Others are not persistence-capable and cannot be made persistence-capable. A third category of classes can be made persistence-capable, but there are important issues to consider when you do so. Be sure to read Java-Supplied Persistence-Capable Classes.
The topics discussed in this section are
Preparing to Run the Postprocessor
Before you run the postprocessor, ensure that the following .zip files or .jar files are explicitly specified in your CLASSPATH. An entry for the directory containing them is not sufficient.
Requirements for Running the Postprocessor
The postprocessor requires specification of
Insert a space between specifications and be sure to specify the required destination parameter. When you run the postprocessor, each batch must have its own destination directory. For example:
osjcfp -dest osjcfpout COM.odi.demo.threads.Institution Banking.zip Account.class
You can specify additional options, which are described in osjcfp: Running the Postprocessor.
Example of Running the Postprocessor
To make the Person class persistence-capable, enter a command like this:
osjcfp -dest ..\osjcfpout Person.class
This command assumes that the Person.class file is in the current directory and the osjcfpout directory is a sibling to the current directory. If the postprocessor successfully generates an annotated version of the Person.class file, it also generates the PersonClassInfo.class file. This file contains information needed by ObjectStore to persistently store instances of Person.
The postprocessor places the annotated Person.class file and the PersonClassInfo.class file in a package-relative subdirectory of the osjcfpout directory. For example, suppose the Person class package name is COM.odi.demo.people. Further suppose that the osjcfpout directory is in the \users\kim directory. The postprocessor writes the annotated class file to a file whose name is made up of the destination directory, the class package, the class name, and the .class extension. It writes the PersonClassInfo.class file to a similar location:
\users\kim\osjcfpout\COM\odi\demo\people\Person.class
\users\kim\osjcfpout\COM\odi\demo\people\PersonClassInfo.class
Note that both of the following commands have the same results, as specified previously.
osjcfp -dest ..\osjcfpout Person.class
osjcfp -dest \users\kim\osjcfpout COM.odi.demo.people.Person
About the Postprocessor Destination Directory
The postprocessor never overwrites the class files specified on the postprocessor command line, unless you specify the -inplace option when you run the postprocessor. If you do specify the ---inplace option, the postprocessor does overwrite the original class files with the annotated class files.
If you specify a destination directory in such a way that it would store the annotated class file in the same location as the unannotated class file, and you do not specify the -inplace option, the postprocessor displays an error message and terminates. It does not produce any class file output.
The postprocessor ignores classes that are rooted in the destination directory. If you try to postprocess a class that exists only in the destination directory (and you do not specify -inplace), the postprocessor reports that it cannot find the file. For example, if you specify the following command when you run osjcfp, you receive an error as shown.
setenv CLASSPATH /usr/devo/java/test:/opt/ODI/osji.zip:/optODI/tools.zip
cd /usr/devo/java/test
javac com/users/jobs/teacher.java
osjcfp -d . com.users.jobs.teacher
...
Error: Class com/users/jobs/teacher could not be found
.
Because the postprocessor ignores the destination directory in the CLASSPATH when it looks up classes, it is unable to locate the specified class. Consequently, the destination directory you specify cannot be the root directory for any of the classes you want to postprocess or any classes referenced by classes you want to postprocess.
Typically, after you run the postprocessor, you have a transient version of a class (your original file) and a persistence-capable version of the class (in the destination directory).
If there are no errors, the postprocessor places a version of all files specified on the command line in the destination directory. The postprocessor annotates those files that require annotations and does not modify those files that do not.
How the Postprocessor Interprets File Names
If a name you specify ends with .class, .zip, or .jar, the postprocessor assumes it is an explicit file name for a class file, .zip file, or .jar file, respectively.
If a name you specify does not end with .class, .zip, or .jar, the postprocessor assumes it is a class name delimited with periods, for example, a.b.C. The postprocessor uses the CLASSPATH environment variable or the -classpath specification on the postprocessor command line to locate the .class file, which can be in a .zip file or .jar file. (The use of the -classpath option does not affect the class path used for the execution of the postprocessor.)
Here is an example of adding the -classpath option. It assumes that you are using ObjectStore on UNIX with the JDK installed in /usr/local/JDK-1.1.
osjcfp -dest osjcfpout -classpath \
/usr/osji:/usr/local/JDK-1.1/java/lib/classes.zip:\
/usr/osji/lib/osji.zip COM.odi.demo.threads.Institution \
Banking.zip Account.class
CLASSPATH and
-classpath
The postprocessor uses the class path you specify in the command line to locate the specified files. This is in place of the CLASSPATH environment variable. At run time, Java implementations append the location of the system classes to the end of the CLASSPATH environment variable. You must do this manually if you specify the -classpath option. This is shown in the examples above as classes.zip.
Order of Processing
The postprocessor processes the class files in the order in which they appear on the command line and according to the persistence mode that is in effect when the postprocessor reaches the file name. The persistence mode indicates whether the postprocessor is
Persistence mode options
The default persistence mode is that the postprocessor generates persistence-capable classes. Here are the options you can specify to determine the persistence mode:
If you specify a .class file or class name, the postprocessor processes it according to the persistence mode that is in effect when the postprocessor reaches the file name. If you specify a .zip file or .jar file the postprocessor processes all class files in the .zip file or .jar file according to the persistence mode that is in effect when the postprocessor reaches the name of the. zip file or .jar file in the command line.
osjcfp -dest osjcfpout -persistaware Tent.class Family.class \
-persistcapable Campers.zip Site.class -copyclass Weather.class
Example
After you run the postprocessor with the previous command,
How the Postprocessor Handles Duplicate File Specifications
It is permissible for a class to be specified more than once in a command line. For example, a file can be in a .zip file and you can also explicitly specify it. Or on UNIX, a file can be included in a wildcard specification and you can also explicitly specify it. In the previous example, the Family class could be in the Campers.zip file. If it were, the postprocessor would annotate the Family class to be persistence-capable. This is because making a class persistence-capable supersedes making it persistence-aware. Likewise, making a class persistence-aware supersedes copying it as is to the destination directory.
If you specify the same class more than once on a command line, both specifications must resolve to the same disk location. For example, suppose you specify both Person.class and COM.odi.demo.people.Person. This is allowed only if the class path causes COM.odi.demo.people.Person to resolve to the same Person.class that is explicitly specified.
How the Postprocessor Handles Files Not Found
The postprocessor must be able to find every file that you specify on the command line. If it cannot find one or more files, it displays an error message and stops processing. It does not produce any annotated class files.
Zip and Jar Files as Input to Postprocessor
If a class originates in a .zip file or .jar file, either because you specify a .zip file or .jar file when you run the postprocessor or because the class path search locates the class in a .zip file or .jar file, the postprocessor writes the annotated class to the package-appropriate subdirectory of the destination directory.
How the Postprocessor Handles Previously Annotated Classes
If the postprocessor previously annotated a .class file, you can only specify that .class file to be copied. You cannot specify it to be annotated. If you do, the postprocessor displays a message that states which specified class was already annotated, and terminates without producing any annotated files.
Troubleshooting OutOfMemory Error
The JDK 1.1 imposes a memory limitation of 16 MB, unless you override it. If you receive a java.lang.OutOfMemory error during postprocessing, you must increase the run-time memory pool. Do one of the following:
setenv OSJCFPJAVA "java -mx32m"
Windows users can enter
set OSJCFPJAVA=java -mx32m
Edit the osjcfp script (Solaris) or osjcfp.bat script (Windows) to incorporate the -mx option in the invocation of Java near the end of the script. On Solaris, the line to change is
$OSJCFPJAVA $javaargs COM.odi.filter.OSCFP $args
On Windows, the line to change is
%osjcfpjava% COM.odi.filter.OSCFP %1 %2 %3 %4 %5 %6 %7 %8
Add -mx32m before the COM.odi.filter.OSCFP entry. This allows the Java virtual machine to increase the heap to 32 MB. You can increase this value further if you need to.
How the Postprocessor Handles Inner Classes
When you define a class inside another class, you must explicitly make both the outer class and the inner class persistence-capable. For example, suppose you define the following class:
Class Foo {
int a;
public class Bar {}
}
You must specify both the Foo class and the Bar class when you run the postprocessor:
osjcfp -dest ../osjcfpout -pc Foo.class -pc Foo$Bar.class
Creating Smaller Annotated Files
To create smaller annotated files, specify the -optimizeclassinfo option when you run the postprocessor. This option prevents the postprocessor from generating xxxClassInfo.java classes for classes that are public or abstract. This reduces the disk footprint and application startup times, since there are fewer classes to load when the application starts.
When the postprocessor does not create a ClassInfo class, it uses the Java reflection API instead. Some of the reflection API is subject to security and access constraints that are enforced to varying degrees depending on the version of the JDK and the platform. In other words, you can use the -optimizeclassinfo option if the Java environment in which you intend to run the application does not restrict the use of the reflection API.
After you run the postprocessor, there are two versions of your class files:
It is important to keep these versions separate because
There are several ways to accomplish this. Object Design recommends that you
To help you manage annotated class files, this section discusses
Ensuring That the Compiler Finds Unannotated Class Files
There are two ways in which the compiler can locate class files:
CLASSPATH is convenient when you compile, but when you try to run your application ObjectStore finds the unannotated files before it finds the annotated files. The -classpath option is more cumbersome to use since it means that the path to Java system classes must be listed explicitly in the argument, but it is safe. It ensures that the compiler does not operate on annotated class files.
Example 1
For example, suppose ObjectStore is installed in c:\osji and you are building an application in c:\app. Your destination directory for annotated class files is c:\app\osjcfpout. Your CLASSPATH variable might look like this:
CLASSPATH=c:\osji\osji.zip;c:\app\osjcfpout;c:\app
When you run the compiler, specify the -classpath option with the following path. This removes the destination directory from the class look-up path and adds the Java classes to the path.
javac -classpath c:\app;c:\osji\osji.zip;c:\jdk117\lib\classes.zip App.java
Example 2
Here is an example of why it is important for the compiler to operate on unannotated class files. Suppose you have two classes named X and Y in the same postprocessor batch. Neither of these classes is explicitly declared to implement COM.odi.IPersistent. Now, suppose you add the following two methods to class Y:
void foo(COM.odi.IPersistent p) {}
void bar() { foo(new X()); } // Trying to pass an X instance to
// a function that is expecting COM.odi.IPersistent
If you recompile only Y.java and the compiler finds the annotated classes, examination of the annotated class file allows the compiler to determine that X implements IPersistent, which allows Y.bar() to compile. If you then recompile both X and Y, the compiler recognizes that X is not declared to implement COM.odi.IPersistent and refuses to compile class Y, even though it successfully compiled earlier.
Ensuring That ObjectStore Finds Annotated Class Files
When you run your application, ObjectStore must find the annotated class files before it finds the unannotated class files. The recommended way to do this is to define a CLASSPATH environment variable that has the postprocessor destination directory before the source file directory.
Example
Consider the following example:
In this scenario, use the following CLASSPATH:
c:\app\osjcfpout;c:\app;c:\odi\osji\osji.zip
After you modify your CLASSPATH environment variable, you can run the postprocessor with no special action. The postprocessor excludes the destination directory from the class path when it does class-path-based searches.
Using the Right Class Files in Complex Applications
There are situations when you want the compiler to read in annotated class files. In these cases, the referenced classes are similar to an independent library on which you are building your application. The referenced classes form a batch, which is a group of class files that must be postprocessed together. The other files in your application form a second batch.
Independent library
For example, suppose this second batch is named X. Specify the -classpath option so that it points to the
This is the most common multiple-batch scenario. Your application is in one batch and the other batches are existing reusable libraries. Each batch has its own postprocessor destination directory.
Classes referenced by other classes
Now suppose that you are not using an existing library. Your application itself contains a group of referenced classes (first batch) and then another group of classes (second batch) that reference the first batch. The following instructions show how to build your application in stages:
- Compile the source files and postprocess the class files in the first batch. (This is the batch of files that are referenced by other classes in the application.)
- Compile the source files in the second batch. You might not want to compile all files in this batch at the same time. Specify the -classpath option to point to the annotated class files in the first batch and any unannotated class files in the second batch.
- Run the postprocessor on the second batch. Specify a destination directory that is different from the destination directory that was specified when the postprocessor operated on the first batch. You can package the result of postprocessing the second batch in a .zip file or .jar file.
Alternatives for Finding the Right Files
In some circumstances, updating your CLASSPATH environment variable might be cumbersome or might not work well with your development environment. (This is true for the Symantec Cafe product.) In these cases, you can copy annotated files back to the building directory. However, if you do this, you must remove the annotated files before you recompile. This ensures that subsequent compilation and postprocessing operates on unannotated class files.
Two other alternatives are to
How the Postprocessor Determines Whether to Generate an Annotated Class File
When you run the postprocessor, it checks if any annotated file it is going to create already exists. If an annotated file does not already exist, the postprocessor generates it. If an annotated file does exist, the postprocessor compares the date on the compiled input file with the date on the annotated output file. If the input file date is after the output file date, the postprocessor generates a new output file. If the input file date is before the output file date, the postprocessor does not generate a new file. It assumes that the annotated file that already exists is still valid.
This works fine when you run the postprocessor repeatedly with the same command line. However, when you change input parameters to the postprocessor, it is a good idea to remove the previously annotated class files from the destination directory. The reason for this is that a comparison of dates might not cause a new annotated file to be generated when the specification of a new input parameter requires a new annotated file to be generated.
To force the postprocessor to overwrite existing annotated files, specify the -f or -force option when you run the postprocessor.
If you know that a class will never need to be stored persistently, you can run the postprocessor to make the class persistence-aware. A persistence-aware class can operate on persistent objects, but it cannot be persistent itself. For an example of how you might use persistence-aware classes, see COM\odi\demo\pport\README.htm.
Persistence-aware annotations require less space than persistence-capable annotations. The postprocessor only adds calls to ObjectStore.fetch() and ObjectStore.dirty() where they are needed to operate on persistent objects. When the postprocessor makes a class persistence-aware, it does not annotate that class's superclass. You only need to make a class persistence-aware, instead of copying it as is, if
You must make a class persistence-aware (or persistence-capable) when it includes methods that obtain arrays from persistent objects.
Specifying the Postprocessor Command Line
To create a persistence-aware class, specify the -pa or -persistaware option followed by the names of the classes that you want to be persistence-aware. For example:
osjcfp -dest osjcfpout -persistaware Compute.class
The preceding command line annotates Compute.class so that it has calls to the fetch() and dirty() methods.
No Changes to Superclasses
Another reason to make a class persistence-aware is that doing so does not require changing its superclasses. This is important for classes such as java.lang.Thread, whose superclass should not be modified. java.lang.Thread is inherently transient, so it makes no sense for it to become persistent because it is not useful when you take it out of the database. Typically, Java system classes are restricted from annotations by the postprocessor.
How the Postprocessor Works
This section describes postprocessor behavior relative to various components in your application. It is important to be familiar with the information here so that the postprocessor produces the results you expect. The topics covered in this section are
Ensuring Consistent Class Files
When you run the postprocessor on more than one class file at a time, all specified classes must be consistent. To ensure class consistency, compile all classes together. The postprocessor does not detect inconsistencies among files it operates on. For example, suppose you modify and recompile a class without also recompiling its subclasses. This can cause inconsistencies, which the postprocessor does not detect when it annotates the class files.
Modifications to Superclasses
When you run the postprocessor to make classes persistence-capable, it generates annotated class files for the specified classes and for any superclasses that are in the same packages as the specified classes. ObjectStore requires annotations to superclasses for all classes that the postprocessor makes persistence-capable. If a superclass is not in the same package as one of its subclasses that is being made persistence-capable, you must explicitly specify the superclass on the postprocessor command line.
Effects on Inheritance
If a class that the postprocessor is annotating has no superclass, other than java.lang.Object, the postprocessor annotates the class to implement the COM.odi.IPersistent interface.
Implementation of the IPersistent interface is mandatory for objects that you want to be persistent. You must define classes so that if they inherit from another class, it is a class that can implement IPersistent.
Every class inherits from the Object class, which defines the hashCode() method and provides a default implementation. For a persistent object, this default implementation often returns a different value for the same persistent object (the object on the disk) at different times. This is because ObjectStore fetches the persistent object into different Java objects at different times (in different transactions or different invocations of Java).
This is not a problem if you never put the object into a persistent hash table or other structure that uses the hashCode() method to locate objects. If you do put them in hash tables or something similar, the hash table or other structure that relies on the hashCode() method might become corrupted when you bring the objects back from the database.
To resolve this problem, you can define your own hashCode() method and base it on the contents of the object so it returns the same thing every time. The signature of this method must be
public int hashCode()
If you do not provide a hashcode() method, the postprocessor adds one if it is necessary. If the default behavior of the postprocessor is not ideal for your application, you can specify the -hashcode and -nohashcode options to control where the postprocessor adds a hashCode() method.
Location of Annotated Class Files
When you run the postprocessor, you must specify a destination directory with the -dest option. The postprocessor uses the destination directory as the root directory of the class hierarchy of annotated files. The postprocessor places the annotated class file in the package-relative subdirectory of the destination directory. With the destination directory specified in your CLASSPATH environment variable, Java can find the annotated classes.
You must create the destination directory before you specify it in an osjcfp command line. The postprocessor creates the required subdirectories in the destination directory.
For example, suppose you specify osjcfpout as the destination directory. When you run the postprocessor on the Person.class file, which is in the COM.odi.demo.people package, the postprocessor places the annotated file in
osjcfpout\COM\odi\demo\people\Person.class
The package name of the annotated class file remains the same, unless you specify an option to change it. The class name of the annotated class file is always the same as the class name of the unannotated class file.
Postprocessor Errors and Warnings
If an error occurs while the postprocessor is running, it terminates without writing any annotated class files.
For any warnings from the postprocessor, you might determine that you can safely ignore the warning. In this case, you can stop the postprocessor from warning you about the field in question. To do so, specify the -quietfield option followed by the fully qualified name of the field for which you want to suppress warnings. Alternatively, you can specify -quietclass to suppress all warnings on the class.
Handling of Final Fields
You cannot make final fields persistent. If you try to do this, the postprocessor displays a warning message and treats the fields marked as final as though you declared them to be transient. To allow such fields to be stored persistently, you must remove the final keyword.
Handling of Static Fields
The postprocessor never stores static fields in the database and never causes the values of static fields to be altered. You must write your own code to update static fields and to store static fields in the database, if that is what you want to do.
Static fields that can hold persistent values
The postprocessor displays a warning for a static field that can hold potentially persistent values. The postprocessor cannot determine the type of the object that will actually be pointed to. Consequently, depending on the type of object referenced, the warning might not be applicable. For example, suppose you have a persistence-capable class named X. The X class has a static member named y of a type that implements COM.odi.IPersistent. When you run the postprocessor, it displays a warning like this:
X.y is a static field of a type that implements COM.odi.IPersistent and that might refer to a persistent object. If this field does refer to a persistent object it must be user maintained.
Referring to a persistent object
If the field mentioned in the warning is intended to refer to a persistent object, you can write your application as follows:
If you specify ObjectStore.RETAIN_STALE when you commit or abort a transaction, you must ensure that you correctly access the objects at the beginning of the next transaction. This is because ObjectStore does not make the object referenced by X.y persistent if it is only reachable from X.y. If ObjectStore makes it persistent because it is reachable from some other point, the object referenced by X.y might become stale at the end of the transaction in which it becomes persistent. If it does, and if the object referenced by X.y does become persistent, it is possible that the application might try to use the stale version of the object.
How can X.y be reachable from some other point? Perhaps some other persistent object or an object that is going to be persistent refers to the object that the static data member is referring to. When ObjectStore commits the transaction and performs transitive persistence, it finds the object that the static data member is referring to.
References to stale objects
An issue to consider is stale references to stale objects. To avoid the inadvertent use of stale objects, update X.y at transaction boundaries. Set X.y to null or to some other value to ensure that if a stale object is referenced by X.y, it is no longer accessible through X.y. Then you can suppress the warning with the -quietfield option.
Summary
For class X, the important points are listed below.
class X { static OSHashtable y = new OSHashtable(); }
Which Java Executable to Use
The postprocessor is a Java program; it requires a Java virtual machine to run. It uses the first Java executable that it finds in your PATH environment variable. If you want the postprocessor to use some other Java executable, set the OSJCFPJAVA environment variable to the name of the Java executable you want the postprocessor to use. The default is java.
If the postprocessor cannot find a Java executable, it generates a Bad command or file name error message.
Line-Number and Local-Variable Information
When the postprocessor annotates a class file, it maintains any existing line-number and local-variable information.
Using a Debugger
The class file postprocessor annotates methods with VM instructions for automatically performing fetch() and dirty() operations on objects. It does this in such a way that the debugging information in the class files remains intact. For the most part, the annotations are invisible to an application. However, it is possible to encounter them under certain circumstances when using a debugger. For example, you might encounter the following when you use the Step into command:
x = foo(y.m);
Stepping into that statement might cause you to enter the ObjectStore code that causes the contents of the y object to be fetched. In such a situation, use the Step out command to leave the ObjectStore code. Then use the Step into command again, which should then step into the call to the foo() method.
You should rely on the Step over command whenever possible. However, there are situations where you must use the Step into command. If you inadvertently step into an ObjectStore method, step out of the ObjectStore code and return to your own code by doing one of the following:
Handling of finalize() Methods
The Java garbage collector calls the java.lang.Object.finalize() method on an object that is no longer referenced. The garbage collector does this before it frees the space occupied by the object. In this way, the finalize() method provides a hook that you can use to free resources that are not freed by garbage collection, for example, memory that was allocated by a native method call.
If your persistence-capable class defines a finalize() method (Object Design recommends that it should not), the class file postprocessor inserts annotations at the beginning of the finalize() method that change the persistent object to a transient object.
Description of Postprocessor Optimizations
The postprocessor optimizes fetch() and dirty() calls in several ways. If you determine that an optimization is preventing insertion of a required call to fetch() or dirty(), you can disable the optimization.
You should disable these optimizations if you commit transactions or evict persistent objects as follows:
If you want to disable all three optimizations, specify the -noopt option instead of the three individual options.
Including Transient and Already Annotated Classes
After you run the postprocessor, the annotated class files are in the package-relative subdirectory of the destination directory (root directory) you specified. You might want other class files in this destination directory. These could be transient (nonpersistence-capable or nonpersistence-aware) class files or files that have already been annotated.
Copying Classes to the Destination Directory
To copy some files to the destination directory along with the annotated files, specify the -copyclass option followed by the name of the file you want to copy. For example:
osjcfp -dest osjcfpout a.zip -copyclass b.class
In this example, the postprocessor annotates the files in a.zip and copies them to the package-relative subdirectory of the osjcfpout directory. The postprocessor also copies b.class to the osjcfpout directory, but it does not modify the b.class file.
You can follow the -copyclass option with one or more .class file names, class names, .jar file names, or .zip file names. This option applies to each name that follows it, until the postprocessor reaches a -pc or -pa option.
Specifying Classes to Be Copied and Classes to Be Persistence-Capable
Classes for which you specify the -copyclass option can overlap with classes for which you specify the -persistcapable or -persistaware option. For example:
osjcfp -dest osjcfpout -copyclass *.class -persistcapable a.class
This allows you to keep all files in a package together and only annotate the classes that need to be annotated. You need not partition classes into those that need annotations and those that do not. You can specify the same file with more than one persistence mode option because the -persistcapable option and the -persistaware option override the -copyclass option.
When Can a Class Be Transient?
Suppose you have a persistence-capable class, class A. A class that refers to class A can be transient if all access to A's nontransient data members is through methods on A. The methods of A will be properly annotated. Since all other classes only use A's methods, the other classes do not need to be persistence-aware. Consequently, you do not need to postprocess any classes that refer to A.
Any class that directly accesses A's nontransient data members must be either persistence-capable or persistence-aware. Any other class that refers to A and does not directly access nontransient data members can be transient. That is, you do not have to postprocess it.
An important exception to this is that if a class manipulates an array object that might be persistent (specifically, setting and getting array elements), that class must be annotated to be persistence-aware. However, if the code that provides access to the array is annotated to access the values of the array, you can avoid making the class persistence-aware. It is difficult to reliably implement this in the general case.
If you compile with optimization the classes that use the methods that get and set array values, the compiler might inline the get and set methods. In this case, you must make the class that uses the get and set methods persistence-aware.
Normally, the postprocessor places the annotated files in a package-relative subdirectory of the destination directory and the annotated files have the same package names as the original files. However, there is an option that allows you to change the package name of files specified in the postprocessor command line. The -translatepackage option modifies the package name so that the persistence-capable version of the class is in one package and the transient version (the original) is in another package.
To help you use the -translatepackage option, this section discusses the following topics:
Using the -translatepackage Option
To create persistence-capable classes whose package name is different from the original package name, specify the -translatepackage option followed by the current package name and then the new package name. The format for this option is
{ -translatepackage | -tp } orig_pkg_name new_pkg_name
For example, suppose you have the a.b.C class and you want to create the d.e.C persistence-capable class. Run the postprocessor like this:
osjcfp -dest osjcfpout -translatepackage a.b d.e C.class
Exact match required
The specification for the original package name must exactly match the package name of the specified file. If there is not an exact match, the postprocessor does not place the annotated file in the new package. For example, suppose you have two classes named COM.odi.demo.New and COM.odi.Old. You want to move COM.odi.Old to the COM.odi.beta package and you specify the following command:
osjcfp -dest osjcfpout -tp COM.odi COM.odi.beta
COM.odi.demo.New COM.odi.Old
The postprocessor places the annotated file for the COM.odi.Old class in COM.odi.beta.Old in the package-relative subdirectory of the osjcfpout directory (osjcfpout\COM\odi\beta\COM.odi.beta.Old.class).
The postprocessor does not place the annotated file for COM.odi.demo.New in a different package because the original package name is COM.odi.demo and not just COM.odi. The postprocessor annotates COM.odi.demo.New and places it in osjcfpout\COM\odi\demo\COM.odi.demo.New.class.
How the Postprocessor Applies the Option
The postprocessor applies the -translatepackage specification to
When copying files
It does not matter whether the postprocessor is making any other changes to the specified files. The postprocessor changes the package names of files for which the -copyclass option is specified right along with new persistence-capable or persistence-aware files.
Multiple option specifications
You can specify this option more than once on a command line to specify several package translations. If you accidentally specify more than one translation for the same package, the postprocessor performs the last translation you specify in the command line.
Updating References to New Package Name
A change to the package name of a class requires updating all references to that class to reflect the new name.
The postprocessor updates the references in classes that it is currently operating on. This includes each class specified on the command line and each class found in a. zip file or .jar file that is specified on the command line.
The postprocessor cannot detect if there are .class files for which the postprocessor was not called that refer to the renamed package. You must either run the postprocessor on the complete set of class files or modify the Java source of any files that the postprocessor is not annotating.
References to Transient and Persistent Versions of a Class
You might want a class to refer to both the transient and persistence-capable versions of some other class.
It is not possible for the postprocessor to determine which references should be to persistence-capable objects. Because of this, you must code the class so it uses the full path name of the different versions of the class. This is the only way to clarify which version of the class is wanted. However, this technique works correctly only when you are operating across batches. It does not work when you are within the same batch.
Example
Here is an example of what that means. Suppose you have a utility class called a.b.C. You want to have both a transient and a persistence-capable version of a.b.C. When you run the postprocessor, you specify -translatepackage to create a persistence-capable version called y.z.C. Then, in another class called a.b.D, you try to use both versions of the class. You write source code in a.b.D that explicitly refers to y.z.C, something like
int n= y.z.C.countThem()
When you try to compile a.b.D, compilation can succeed only if you put the annotated classes into the class path of the compiler. Otherwise, the compiler reports an error, because there is no such thing as y.z.C.
Also, it is not possible for a.b.C and a.b.D to be in the same batch, because the -translatepackage option would apply to a.b.D. This would make all of a.b.D's calls go to the persistence-capable version, which is not what you want.
Steps to follow
To use persistence-capable and transient versions of the same class, follow these steps:
- Create a utility library.
This is the first batch. This library creates transient versions of the class.
- Run the postprocessor on the first batch and specify options that put the two different versions of the class in two different packages.
This step creates the persistence-capable version of the class.
- Use the library from an application.
The application is the second batch.
- Compile the application with the annotated files of the first batch, but not the second batch, in the compiler's class path.
References to Transient Instances of a Persistence-Capable Class
You can use instances of a persistence-capable class in a transient-only fashion. No special action is required and the calls to ObjectStore.fetch() and ObjectStore.dirty() do nothing.
There is no need for the unannotated version of the class to be available at run time.
To use the annotated version of the class, even if you are using it transiently, the osji.zip or stublib.zip file must be available in the CLASSPATH at run-time. If you are only using the class transiently, it can be the stublib.zip that is available.
Creating Persistence-Capable Classes with Transient Fields
You can create a persistence-capable class with transient fields. A transient field is a field that is not stored in the database. The postprocessor ignores transient fields. Use the transient keyword to create a transient field. For example:
class A {
transient java.awt.Component myVisualizationComponent;
int myValue;
...
}
In this class, the myVisualizationComponent field is declared to be a transient reference to java.awt.Component. java.awt is a package that contains GUI classes that do not lend themselves to being persistence-capable.
In your persistence-capable class, you might have transient fields that you want to be able to access outside a transaction. In this situation, you can specify the -noannotatefield or -naf option for the field when you run the postprocessor. This option prevents access to the specified field from causing fetch() and dirty() calls on the containing object. Normally, access to a transient field causes fetch() or dirty() to be called to allow the postInitializeContents() and preFlushContents() methods to convert between persistent and transient state.
Transient Fields and Serialization
If you have a class that has fields that are declared as transient, this causes the default handling of these fields by object serialization to be to ignore the fields. If you want them ignored by object serialization and you also want them to be stored persistently, specify the -ignoretransient option for the class when you run the postprocessor.
On the other hand, there might be a field that must be available for object serialization, but you do not want to store that field in the database. In this situation, specify the -transientfield option for the field when you run the postprocessor. This option causes the postprocessor to treat the specified field as though it has a transient modifier, even if it does not.
Initialization of Some Transient Fields
In the declaration of a transient field in a persistence-capable class, you might want to initialize the value of the transient field. However, when the postprocessor creates the hollow object constructor for the class, it does not define the constructor to initialize the transient field. This is true even when you specify the final keyword. The postprocessor does not initialize such fields, because the initialization occurs as inlined code in each of the constructors for the class. For example:
private transient final MyField myField = new MyField();
The final keyword indicates to the postprocessor that initialization is required. However, the initialization code is not readily available and myField is not initialized. There are several ways to handle this situation.
You can create the hollow object constructor manually. For example, suppose you define the MyField class, which extends the MyFarm class:
...
public MyField(COM.odi.ClassInfo dummyClassInfo) {
super(dummyClassInfo);
}
This requires you to also manually define a hollow object constructor for the MyFarm class, and for each superclass of the myFarm class.
Alternatively, you can remove the final qualifier and initialize the transient field in an IPersistent.postInitializeContents() method.
If you include an inline initialization of a field declared to be transient and final, the postprocessor displays an error message and stops processing. If you include an inline initialization of a field declared to be transient, but not final, the postprocessor warns you about the situation and continues processing. If you determine that you can safely ignore the message, you can turn it off with the -ignoretransient option to the postprocessor.
See also Transient Fields in Persistence-Capable Classes.
Customizing Updated Classes
There are several ways you can customize persistence-capable and persistence-aware annotations. You can implement your own versions of methods that the postprocessor typically adds. You can implement hook methods that ObjectStore calls at specified points. You can define a hollow object constructor in place of the hollow object constructor the postprocessor typically defines. You can also insert your own fetch() and dirty() calls.
Implementing Customized Methods and Hook Methods
The three methods described below are among the several annotations that the postprocessor adds to persistence-capable classes.
Alternatives
If you want to, there are two ways that you can customize the behavior of these methods:
Warning
The body of a hook method must not call any methods of the class and must not start or end a transaction. This is because the class methods are annotated and consequently make calls to fetch() and dirty(). Such calls in the middle of initializing or writing the object are not allowed because they might cause the virtual machine to encounter a stack overflow.
Here is an example of a program that implements these hook methods.
import COM.odi.*;
/**
* PColor provides a persistent representation of colors that can be
* used with the Java AWT package. The java.awt.Color class itself
* cannot be stored persistently, because some of its internal state
* depends on the particular kind of color display being used. If a
* java.awt.Color were created on a computer that used a 24-bit-deep
* color monitor, stored in a database, and then retrieved and used
* on a different computer that had a gray-scale monitor, it would not
* function correctly. PColor stores the color value as three
* integers, and then recreates the java.awt.Color object whenever
* the PColor object is brought into Java from persistent storage.
*
* For expository purposes, this example pretends that the value of a
* java.awt.Color object can change after the object is created. The
* real java.awt.Color class is immutable, and so the setBlue method
* below would not work, and the preFlushContents method would
* not actually be needed.
*/
public class PColor {
/*These instance variables are stored persistently. They represent
the color value. */
int red;
int green;
int blue;
/*This instance variable is declared transient, so it is not stored
persistently. It is managed by the methods below. */
transient java.awt.Color color;
PColor(int r, int g, int b) {
red = r;
green = g;
blue = b;
color = new java.awt.Color(r, g, b);
}
/*When a PColor is brought into Java from persistent storage, the
java.awt.Color object is created. Note that this method runs
after the initializeContents, so that it can use the values
of the persistent instance variables. */
public void postInitializeContents() {
color = new java.awt.Color(red, green, blue);
}
/*When a PColor is sent out from Java to persistent storage, the
color value from the java.awt.Color object is copied into the
persistent instance variables, so that it will be saved.
Note that this method runs before flushContents, so that it
can set up the values of the persistent instance variables. */
public void preFlushContents() {
red = color.getRed();
green = color.getGreen();
blue = color.getBlue();
}
/*When clearContents happens, this method sets the color
instance variable to null, so that this PColor object won't be
stopping the java.awt.Color object from being reclaimed. */
public void preClearContents() {
color = null;
}
/*Equality for PColor objects is the same as equality of the
underlying java.awt.Color objects. */
public boolean equals(Object obj) {
if (obj instanceof PColor) {
return color.getRGB() == ((PColor)obj).color.getRGB();
}
return false;
}
public java.awt.Color getColor() {
return color;
}
public int getBlue() {
return color.getBlue();
}
public int setBlue(int b) {
color.setBlue(b);
}
/* and so on.... */
}
Creating a Hollow Object Constructor
For each persistence-capable class, the postprocessor finds or generates a hollow object constructor. The hollow object constructor takes a single argument whose type is COM.odi.ClassInfo. Typically, you do not need to define a hollow object constructor, but you can if you want to.
Why define one?
A reason to define your own hollow object constructor is to initialize transient fields that you want to be usable even if the fetch() method has not been called.
You should avoid performing actions in a hollow object constructor that would cause the object to be fetched. Doing so might cause infinite recursion to occur.
For example, if a class has a persistent hashCode() method, it is a bad idea to define a hollow object constructor to register the instances of the class in a hash table. Doing so would cause the hashCode() method to be called, which in turn would attempt to fetch the object.
Creation steps
When the postprocessor creates the hollow object constructor, it follows these steps:
- The postprocessor selects an appropriate superclass hollow object constructor.
If the superclass has an accessible constructor that takes a single COM.odi.ClassInfo argument, or if it will have one because the postprocessor will add it during this execution of the tool, the postprocessor uses that constructor. The postprocessor reports an error if it cannot find an accessible constructor.
- The postprocessor creates a public constructor that
You can define the hollow object constructor instead of allowing the postprocessor to do it. If you define one, the postprocessor does not generate one.
Before an application can access the contents of a persistent object, it must call the ObjectStore.fetch() method to read the object or the ObjectStore.dirty() method to modify the object. These calls make the contents of the object available to your application. The postprocessor inserts these calls in methods of classes that it makes persistence-capable or persistence-aware. However, the postprocessor might not annotate your code for best performance. You might find that you can improve performance by inserting the fetch() and dirty() calls yourself.
Caution
If you insert a fetch() or dirty() call in a method, the postprocessor does not add any additional fetch() or dirty() calls to that method.
Procedure for Optimizing Operations
Before you add the calls yourself, first allow the postprocessor to add the fetch() and dirty() calls. Then run and monitor your program. If you want to try to improve performance, add the calls to your source file and recompile. When you run the postprocessor again, it recognizes that the fetch() or dirty() call is already in place and does not add any fetch() or dirty() calls to any methods that already contain such a call.
If you do this annotation, you should also add implements IPersistent to the definition of any class that is accessed with a fetch() or dirty() call. When you do this, the compiler can effectively use the multiple overloadings of the fetch() and dirty() methods, which take COM.odi.IPersistent arguments. Also, the compiler can generate more efficient code when you declare the class to implement IPersistent in your source.
Inlining Code
An important consideration when annotating by hand is that the compiler might inline the code into calling methods. This makes it appear to the postprocessor that the code annotations are in the calling method, which might not be true.
When using the JDK javac compiler, this occurs when you specify the -O (capital O, as in Oslo) option.
To ensure that the postprocessor functions correctly, you must do one of the following:
Preventing Fetch of Transient Fields
You might want to avoid the insertion of the fetch() call in methods that operate only on transient fields. A strategy for doing this takes advantage of the fact that the postprocessor does not annotate a method if it already includes a fetch() or dirty() call. If you know that a method operates on only transient fields, you can prevent insertion of the fetch() call with code such as the following:
try {
method body goes here
} catch ( SomeRuntimeExceptionThatWillNotOccur) {
ObjectStore.fetch(this);
}
This imposes no execution time and prevents the postprocessor from inserting the fetch() method. You can create your own exception, which inherits from java.lang.RuntimeException, or select an existing one. The safest approach is to create your own exception so that you can be sure that the exception is never thrown.
Specifying the Number of Array Dimensions in Persistence-Capable Classes
By default, three is the maximum number of dimensions in a persistent array. If you need a persistent array that has more than three dimensions, you can run the postprocessor with the -a or -arraydims option followed by an integer, which specifies the new maximum number of array dimensions. This option applies to all classes that the postprocessor annotates during this execution of the tool. It does not apply to a class that the postprocessor does not annotate. For example:
osjcfp -dest osjcfpout -a 4 Person.class Pet.class -copyclass
Car.class
This allows arrays of type Person and Pet to have as many as four dimensions. The maximum number of array dimensions does not change for the Car class because the postprocessor does not annotate that file, it only copies it to the destination directory.
An alternative, and far more complex, way to increase the number of allowed array dimensions is to manually implement the class of the ClassInfo instance associated with the persistence-capable class.
Performing a Test Run of the Postprocessor
You can run the postprocessor without actually updating any files. The tool performs all processing and error checking and can display messages that indicate what it is doing. This allows you to make corrections before creating the persistence-capable versions of your classes.
To perform a test run of the postprocessor, specify the -nowrite option on the command line. For example:
osjcfp -dest osjcfpout -nowrite classes.zip
This command processes all class files in the .zip file and displays any error messages. To view information messages from the postprocessor, include the -verbose option. For example:
osjcfp -dest osjcfpout -nowrite -verbose classes.zip
It does not matter where you place the -nowrite or -verbose option in the command line. Wherever you place them, they apply to all files that the postprocessor processes.
To suppress nonfatal warning messages, specify the -quiet option. The -quiet and -verbose options are mutually exclusive. The last one used on the command line applies to the entire execution. For example, the following line suppresses warning messages during the processing of all specified files because the -quiet option comes after the -verbose option.
osjcfp -dest osjcfpout -nowrite -verbose classes.zip -quiet more.zip
You can also suppress some warnings but not all warnings. Specify the -quietclass option followed by the fully qualified name of a class to suppress warnings for that class. Specify the -quietfield option followed by the fully qualified name of a field to suppress warnings that pertain to that field. These options apply only to the element whose name immediately follows the option. If the -verbose option is also specified, these options take precedence.
Using an Input File
When you are running the postprocessor on a lot of files and specifying many options, the command line can be very long. As a convenience, you can enter the options and file names in a file and then specify the file name as a postprocessor option. Be sure to prefix the file name with the @ symbol.
Windows
On Windows systems, there is a limit of eight arguments on a command line. Consequently, you usually must use input files on Windows.
Format
You can include comments in the input file. You can place items on different lines and line continuation symbols are not required. Line breaks are treated as white space. Otherwise, enter data in the input file exactly as you would enter it on the command line.
Indicate comments with a # sign. The postprocessor ignores any subsequent characters on the same line as the # sign.
Example
For example, suppose you enter some postprocessor options and files for the postprocessor to operate on in an input file named optionsAndFiles. You specify this file as follows:
osjcfp @optionsAndFiles
You can intersperse input file specifications with options and files that you enter on the command line. For each specified input file, the postprocessor removes any comments from the input file and replaces the input file specification with the data in the input file. The postprocessor then begins to process the command line. For example:
osjcfp -dest osjcfpout @file1 -tp old.pack new.pack @file2
The postprocessor
- Replaces @file1 with the contents of file1.
- Replaces @file2 with the contents of file2.
- Executes the command line starting with the -dest option.
Nesting and wildcards
You cannot nest input file specifications. That is, you cannot include the @ file_name option in an input file. Also, you cannot use wildcards in an input file. The postprocessor does not expand them.
Annotations You Must Add
There are some annotations that the postprocessor either cannot perform or does not perform because of execution performance considerations. You must include these annotations when you code your source files.
Keep in mind that when you add even one fetch() or dirty() call to a method, the postprocessor recognizes that the method is already annotated and does not add any other fetch() or dirty() calls to that method. If you do annotate a method, be sure to add all required calls.
This section provides information about the following topics:
Interfacing with Nonpersistent Methods
It is possible for a method in a persistence-capable class to pass a persistent object to a nonpersistent method. When this happens, you must ensure that there is a fetch() or dirty() call for the persistent object before it is passed to the nonpersistent method.
If all access to persistent objects is through annotated methods (methods in persistence-capable or persistence-aware classes), then manual annotations are not required. For arrays, there is no way to define a class so that arrays of that class can only be accessed by persistence-aware classes. You must be sure to call the fetch() or dirty() method on a persistent array before passing it to a method in a nonpersistent class.
Interfacing with Native Classes
The postprocessor cannot analyze or annotate native methods. If your code passes a persistent object to a native method, and if the native code might try to access the object other than through annotated methods, be sure to insert a call to fetch() or dirty() for the persistent object before it is passed. In cases where native code might access and/or navigate among persistent objects, you must do one of the following:
Annotating Subclasses
After you create a persistence-capable or persistence-aware class, you can define a subclass of that class. Doing so does not make the subclass persistence-capable or persistence-aware. You must run the postprocessor on the subclass.
If you forget to run the postprocessor on a subclass and if the subclass is reachable from a persistent root (other than through a transient field), ObjectStore might try to migrate instances of the subclass to the database. This attempt causes an error because the subclass is not persistence-capable.
Passing Arrays
In your application, you might pass an array to a nonpersistent method when the nonpersistent method is defined as having a parameter of type java.lang.Object. In this situation, the postprocessor cannot determine that it should insert fetch() or dirty() calls for the array in the calling method before passing the array. You must annotate the calling method yourself.
If the called method is declared to accept an array argument, the postprocessor recognizes that a fetch() call might be needed and inserts it.
Implementing the Hollow Object Constructor for Some Instance Fields
A class can include nonstatic (instance) fields that contain initializer expressions in their declarations. Postprocessor-generated ClassInfo constructors do not run these initializers. Normally, this is not a problem. The constructor allows hollow object initialization and the initializeContents() method overwrites these fields when the object is fetched.
However, there might be transient nonstatic fields that have initializer expressions or fields that are treated as transient by your implementation of the ClassInfo type and the initializeContents() and flushContents() methods. In this case, you must manually implement the hollow object constructor or ObjectStore does not run the initializer. It is impossible for the postprocessor to detect such cases, and no warning message can be provided.
Using the Java Reflection API with Persistence-Capable Objects
You can use the java.lang.reflect.Field class to get and set fields of persistence-capable objects. To do so, you must
Class File Postprocessor Limitations
It is possible to cause invalid references when you run the postprocessor and rename the package. In an annotated class, the postprocessor locates and updates class names if they are in field, method, or class references. The postprocessor cannot locate and update string arguments to Class.forName() if the name specifies a class whose package has been renamed.
[previous] [next]
Copyright © 1998 Object Design, Inc. All rights
reserved.
Updated: 10/07/98 08:46:04