FileDocCategorySizeDatePackage
Main.javaAPI DocGlassfish v2 API38491Fri May 04 22:34:26 BST 2007com.sun.jdo.api.persistence.enhancer

Main.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */


package com.sun.jdo.api.persistence.enhancer;

//@olsen:
//import java.io.*;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.io.FileReader;
import java.io.BufferedReader;

//@olsen:
//import java.util.*;
import java.util.Map;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipException;

import java.net.URL;

import com.sun.jdo.api.persistence.model.Model;

//import com.sun.jdo.api.persistence.enhancer.util.ClassFileSource;
//import com.sun.jdo.api.persistence.enhancer.util.ZipFileRegistry;
//import com.sun.jdo.api.persistence.enhancer.util.ClassPath;
//import com.sun.jdo.api.persistence.enhancer.util.FilePath;

//@olsen: added support for timing statistics
import com.sun.jdo.api.persistence.enhancer.util.Support;

//import com.sun.jdo.api.persistence.enhancer.classfile.ClassFile;

import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaData;
import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataModelImpl;
import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataPropertyImpl;
import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataTimer;

//import com.sun.jdo.api.persistence.enhancer.impl.ClassControl;
//import com.sun.jdo.api.persistence.enhancer.impl.Environment;


//import org.openidex.jarpackager.ArchiveEntry; //@yury Added the capability to create ClassFileSources out of ArchiveEntries

//@olsen: disabled feature
/*
import com.sun.jdo.api.persistence.enhancer.impl.FieldMap;
*/

//@lars: moved functionality into ByteCodeEnhancerHelper
//@lars: design improvements
//@olsen: cosmetics
//@olsen: subst: [iI]Persistent -> [pP]ersistenceCapable
//@olsen: subst: /* ... */ -> // ...
//@olsen: moved: class FilterError -> package util
//@olsen: moved: OSCFP.addClass(ClassControl) -> impl.Environment
//@olsen: subst: filterEnv.classMap.elements() -> filterEnv.getClasses()
//@olsen: subst: filterEnv.classMap.get(name) -> filterEnv.getClass(name)
//@olsen: subst: filterEnv.translations -> filterEnv.translations()
//@olsen: subst: filterEnv.translations -> filterEnv.destinationDirectory()
//@olsen: subst: OSCFP -> Main
//@olsen: subst: filterEnv -> env
//@olsen: subst: FilterEnv -> Environment
//@olsen: dropped parameter 'Environment env', use association instead
//@olsen: subst: augment -> closeOver
//@olsen: subst: collectAllClasses -> collectClasses
//@olsen: subst: Vector -> Collection, List, ArrayList
//@olsen: subst: Hashtable -> Map, HashMap
//@olsen: subst: Enumeration,... -> Iterator, hasNext(), next()
//@olsen: moved: Main.closeOverClasses() -> EnhancerControl
//@olsen: moved: Main.closeOverClass(ClassControl) -> EnhancerControl
//@olsen: moved: Main.modifyClasses() -> EnhancerControl
//@olsen: moved: Main.retargetClasses() -> EnhancerControl
//@olsen: moved: Main.locateTranslatedClasses() -> EnhancerControl
//@olsen: moved: Main.checkIndexableFields() -> EnhancerControl
//@olsen: subst: (ClassControl) control -> cc
//@olsen: removed: proprietary support for HashCode
//@olsen: removed: proprietary support for TypeSummary
//@olsen: removed: proprietary support for ClassInfo
//@olsen: removed: proprietary support for IndexableField
//@olsen: removed: support for IgnoreTransientField, AddedTransientField
//@olsen: removed: support for [No]AnnotateField


/**
 * Main is the starting point for the persistent filter tool.
 */
public class Main
{


    /**
     *  The byte code enhancer.
     */
    private ByteCodeEnhancer enhancer = null;


    /**
     *  The stream to write messages to.
     */
    private final PrintWriter outMessages = new PrintWriter (System.out, true);


    /**
     *  The stream to write error messages to.
     */
    private final PrintWriter outErrors = new PrintWriter (System.err, true);


    /**
     *  The command line options.
     */
    private final CmdLineOptions cmdLineOpts = new CmdLineOptions ();


    /**
     * Construct a filter tool instance
     */
    public Main() {
    }


    /**
     * This is where it all starts.
     */
    public static void main(String[] argv) {
        //@olsen: added support for timing statistics
        final Main main = new Main();
        try {
            //@olsen: split: method filter() -> process(), processArgs()
            //System.exit(new Main().process(argv));
            main.process(argv);
        } catch (RuntimeException tt) {
            //} catch (Throwable tt) {
            //@olsen: changed to print to error stream
            System.err.println("Exception occurred while postprocessing:");
            tt.printStackTrace(System.err);
            //System.exit(1);
            throw tt;
        } finally {
            //@olsen: added support for timing statistics
            if (main.cmdLineOpts.doTiming) {
                Support.timer.print();
            }
        }
    }

    /**
     * Process command line options and perform filtering tasks
     */
    //@olsen: split: method filter() -> process(), processArgs()
    public int process(String[] argv) {
        //@olsen: added inplace of disabled feature
        ArrayList cNames = new ArrayList();
        //@olsen: split: method filter() -> process(), processArgs()
        int res = processArgs(argv, cNames);
        if (res != 0) {
            //@olsen: added println()
            printMessage ("aborted with errors.");//NOI18N
            return res;
        }

        //@olsen: added support for timing statistics
        try {
            if (this.cmdLineOpts.doTiming) {
                Support.timer.push("Main.process(String[])");//NOI18N
            }

            // Find all of the classes on which we want to operate
//@olsen: disabled feature
            enhanceInputFiles (cNames);
/*
            computeClasses(pcNames, paNames, ccNames);
*/

            printMessage ("done.");//NOI18N
            return 0;
        } finally {
            if (this.cmdLineOpts.doTiming) {
                Support.timer.pop();
            }
        }
    }

    /**
     * Process command line options
     */
    //@olsen: split: method filter() -> process(), processArgs()
    //@olsen: made private
    protected int processArgs(String[] argv,
                            //@olsen: added inplace of disabled feature
                            Collection cNames) {
        argv = preprocess(argv);

//@olsen: disabled feature
/*
        ArrayList ccNames = new ArrayList();
        ArrayList pcNames = new ArrayList();
        ArrayList paNames = new ArrayList();
        int classMode = ClassControl.PersistCapable;
*/

//@olsen: disabled feature
/*
        String classpath = System.getProperty("java.class.path");
        String sysClasspath = System.getProperty("sun.boot.class.path", "");
*/

        //@olsen: added
        Properties jdoMetaDataProperties = null;

        for (int i=0; i<argv.length; i++) {
            //@olsen: improved control flow, subst: else -> continue

            String arg = argv[i];
//@olsen: disabled feature
/*
            if (arg.equals("-cc") ||
                arg.equals("-copyclass")) {
                classMode = ClassControl.PersistUnknown;
                continue;
            }
            if (arg.equals("-pa") ||
                arg.equals("-persistaware")) {
                classMode = ClassControl.PersistAware;
                continue;
            }
            if (arg.equals("-pc") ||
                arg.equals("-persistcapable")) {
                classMode = ClassControl.PersistCapable;
                continue;
            }
*/
            if (arg.equals("-v") ||//NOI18N
                arg.equals("-verbose")) {//NOI18N
                this.cmdLineOpts.verbose = true;
                this.cmdLineOpts.quiet = false;
                continue;
            }
            if (arg.equals("-q") ||//NOI18N
                arg.equals("-quiet")) {//NOI18N
                this.cmdLineOpts.quiet = true;
                this.cmdLineOpts.verbose = false;
                continue;
            }
            if (arg.equals("-f") ||//NOI18N
                arg.equals("-force")) {//NOI18N
                this.cmdLineOpts.forceWrite = true;
                continue;
            }
//@olsen: disabled feature
/*
            if (arg.equals("-inplace")) {
                env.setUpdateInPlace(true);
                continue;
            }
*/
//@lars: disabled feature
/*
            if (arg.equals("-qf") ||//NOI18N
                arg.equals("-quietfield")) {//NOI18N
                if (argv.length-i < 2) {
                    usage();
                    printError ("Missing argument to the -quietfield option", null);//NOI18N
                } else {
                    String fullFieldName = argv[++i];
                    if (fullFieldName.indexOf('.') == -1) {
                        printError ("Field name specifications must include " +//NOI18N
                                  "a fully qualified class name.  " +//NOI18N
                                  fullFieldName + " does not include one.", null);//NOI18N
                    } else {
                        env.suppressFieldWarnings(fullFieldName);
                    }
                }
                continue;
            }
            if (arg.equals("-qc") ||//NOI18N
                arg.equals("-quietclass")) {//NOI18N
                if (argv.length-i < 2) {
                    usage();
                    env.error("Missing argument to the -quietclass option");//NOI18N
                } else {
                    env.suppressClassWarnings(argv[++i]);
                }
                continue;
            }
*/
            if (arg.equals("-nowrite")) {//NOI18N
                this.cmdLineOpts.noWrite = true;
                continue;
            }
//@olsen: disabled feature
/*
            if (arg.equals("-modifyjava")) {
                env.setModifyJavaClasses(true);
                continue;
            }
*/
//@olsen: disabled feature
/*
            if (arg.equals("-modifyfinals")) {
                env.setAllowFinalModifications(true);
                continue;
            }
*/
//@olsen: disabled feature
/*
            if (arg.equals("-noarrayopt")) {
                env.setNoArrayOptimization(true);
                continue;
            }
*/
//@lars: disabled feature
/*
            if (arg.equals("-nothisopt")) {//NOI18N
                env.setNoThisOptimization(true);
                continue;
            }
            if (arg.equals("-noinitializeropt")) {//NOI18N
                env.setNoInitializerOptimization(true);
                continue;
            }
            if (arg.equals("-noopt")) {//NOI18N
                env.setNoOptimization(true);
                continue;
            }
*/
            if (arg.equals("-d") ||//NOI18N
                arg.equals("-dest")) {//NOI18N
                if (argv.length-i < 2) {
                    printError ("Missing argument to the -dest option", null);//NOI18N
                    usage();
                }
                this.cmdLineOpts.destinationDirectory = argv[++i];
                continue;
            }
//@olsen: disabled feature
/*
            if (arg.equals("-classpath") ||
                arg.equals("-cpath")) {
                if (argv.length-i < 2) {
                    usage();
                    env.error("Missing argument to the -classpath option");
                }
                classpath = argv[++i];
                continue;
            }
            if (arg.equals("-sysclasspath") ||
                arg.equals("-syscpath")) {
                if (argv.length-i < 2) {
                    usage();
                    env.error("Missing argument to the -sysclasspath option");
                }
                sysClasspath = argv[++i];
                continue;
            }
*/
//@olsen: disabled feature
/*
            if (arg.equals("-tp") ||
                arg.equals("-translatepackage")) {
                if (argv.length-i < 3) {
                    usage();
                    env.error("Missing arguments to the -translatepackage option");
                }
                env.setPackageTranslation(argv[i+1], argv[i+2]);
                i += 2;
                continue;
            }
*/
            //@olsen: new command line option for timing statistics
            if (arg.equals("-t") ||//NOI18N
                arg.equals("--doTiming")) {//NOI18N
                this.cmdLineOpts.doTiming = true;
//                env.setDoTimingStatistics(true);
                continue;
            }
            //@olsen: new command line option for JDO meta data properties
            if (arg.equals("-jp") ||//NOI18N
                arg.equals("--jdoProperties")) {//NOI18N
                if (argv.length-i < 2) {
                    printError("Missing argument to the -jp/--jdoProperties option", null);//NOI18N
                    usage();
                }
                try {
                    jdoMetaDataProperties = new Properties();
                    jdoMetaDataProperties.load(new FileInputStream(argv[++i]));
                } catch (IOException ex) {
                    printError("Cannot read JDO meta data properties from file", ex);//NOI18N
                    usage();
                }
                continue;
            }
            if (arg.length() > 0 && arg.charAt(0) == '-') {
                printError("Unrecognized option:" + arg, null);//NOI18N
                usage();
            }
            if (arg.length() == 0) {
                printMessage ("Empty file name encountered on the command line.");//NOI18N
            }

            //@olsen: added inplace of disabled feature
            cNames.add(arg);
//@olsen: disabled feature
/*
            {
                if (arg.equals("java.lang.Object"))
                    env.error("java.lang.Object may not be postprocessed");
                else if (classMode == ClassControl.PersistCapable)
                    pcNames.add(arg);
                else if (classMode == ClassControl.PersistAware)
                    paNames.add(arg);
                else if (classMode == ClassControl.PersistUnknown)
                    ccNames.add(arg);
                else
                    affirm(false, "Invalid class mode");
            }
*/
        }

        //@olsen: forced settings
        //env.setVerbose(true);
        this.cmdLineOpts.quiet = false;
//        env.setNoOptimization(true);
//        env.message("forced settings: -noopt");//NOI18N

/*
        if (env.errorCount() > 0)
            return 1;
*/

//@olsen: disabled feature
/*
        env.setClassPath(classpath +
                         java.io.File.pathSeparator +
                         sysClasspath);
*/

        // The user must specify a destination directory
        if (this.cmdLineOpts.destinationDirectory == null) {
            if (argv.length > 0)
                printError("No -dest output directory was specified", null);//NOI18N
            usage();
        }

        //@olsen: added: initialize JDO meta data
        JDOMetaData jdoMetaData;
        if (jdoMetaDataProperties != null) {
            printMessage("using JDO meta-data from properties");//NOI18N
            jdoMetaData = new JDOMetaDataPropertyImpl(jdoMetaDataProperties, this.outMessages);
        } else {
            printMessage("using JDO meta-data from Model.Enhancer");//NOI18N
            jdoMetaData = new JDOMetaDataModelImpl(Model.ENHANCER, this.outMessages);
        }
        //@olsen: added support for timing statistics
        if (this.cmdLineOpts.doTiming) {
            // wrap with timing meta data object
            jdoMetaData = new JDOMetaDataTimer(jdoMetaData);
        }

        try
        {
            this.enhancer = createEnhancer (jdoMetaData);
        }
        catch (Exception ex)
        {
            printError ("Error creating the enhancer", ex);
        }


//@olsen: disabled feature
/*
        // -translatepackage is incompatible with in-place update.
        if (env.updateInPlace() && env.translations().size() > 0) {
            env.error("The -translatepackage option cannot be used " +
                      "in conjunction with the -inplace option.");
            return 1;
        }
*/

        // make sure we don't lookup classes from the destination directory
        // unless we are doing in-place annotation, and in that case it should
        // be as a last resort.
//@lars: removed
/*
        if (env.updateInPlace())
            env.moveDestinationDirectoryToEnd();
        else
            env.excludeDestinationDirectory();
*/

        //@olsen: split: method filter() -> process(), processArgs()
        return 0;
    }

    // Private methods

    /**
     * Preprocess @files, returning a new argv incorporating the contents
     * of the @files, if any
     */
    private String[] preprocess(String[] args) {
        ArrayList argVec = new ArrayList();
        for (int i=0; i<args.length; i++) {
            if (args[i].length() > 0 && args[i].charAt(0) == '@') {
                String filename = null;
                if (args[i].length() == 1) {
                    if (i+1 < args.length)
                        filename = args[++i];
                } else {
                    filename = args[i].substring(1);
                }

                if (filename == null) {
                    printError("missing file name argument to @.", null);//NOI18N
                } else {
                    appendFileContents(filename, argVec);
                }
            } else {
                argVec.add(args[i]);
            }
        }
        //@olsen: subst: Vector -> ArrayList
        //String[] newArgs = new String[argVec.size()];
        //argVec.copyInto(newArgs);
        final String[] newArgs = (String[])argVec.toArray(new String[0]);
        return newArgs;
    }

    /**
     * Given an input file name, open the file and append each "word"
     * within the file to argVec.  This currently has only a very
     * primitive notion of words (separated by white space).
     */
    private void appendFileContents(String filename, ArrayList argVec) {
        try {
            FileReader inputFile = new FileReader(filename);
            try {
                BufferedReader input = new BufferedReader(inputFile);
                String s = null;
                while ((s = input.readLine()) != null) {
                    StringTokenizer parser = new StringTokenizer(s, " \t", false);//NOI18N
                    while (parser.hasMoreElements()) {
                        String token = parser.nextToken();
                        if (token.length() > 0 && token.charAt(0) == '@')
                            printError("The included file \"" +//NOI18N
                                      filename +
                                      "\" contains a recursive include.  " +//NOI18N
                                      "Recursive includes are not supported.", null);//NOI18N
                        if (token.charAt(0) == '#') break;
                        argVec.add(token);
                    }
                }
            }
            catch (IOException ex) {
                printError("IO exception reading file " + filename + ".", ex);//NOI18N
            }
        }
        catch (FileNotFoundException ex) {
            printError("file " + filename + " not found.", ex);//NOI18N
        }
    }


    /**********************************************************************
     *
     *********************************************************************/

    private final ByteCodeEnhancer createEnhancer (JDOMetaData jdometadata)
                                   throws EnhancerUserException,
                                          EnhancerFatalError
    {

        Properties props = new Properties ();
        if  (this.cmdLineOpts.verbose)
        {
            props.put (FilterEnhancer.VERBOSE_LEVEL, FilterEnhancer.VERBOSE_LEVEL_VERBOSE);
        }

        return new FilterEnhancer (jdometadata, props, this.outMessages, this.outErrors);

    }  //Main.createEnhancer()


    /**********************************************************************
     *  Enhances all files entered in the command line.
     *
     *  @param  filenames  The filenames.
     *********************************************************************/

    private final void enhanceInputFiles (Collection filenames)
    {

        for (Iterator names = filenames.iterator(); names.hasNext ();)
        {
            try
            {
                String name = (String) names.next ();
                int n = name.length ();

                //if we have a class-files
                InputStream in = null;
                if (isClassFileName (name))
                {
                    enhanceClassFile (openFileInputStream (name));
                }
                else
                {
                    //if we have an archive
                    if (isZipFileName (name))
                    {
                        enhanceZipFile (name); //getZipFile (name));
                    }
                    //assume that it is a class name
                    else
                    {
                        enhanceClassFile (openClassInputStream (name));
                    }
                }
            }
            catch (Throwable ex)
            {
                printError (null, ex);
            }
        }

    }  //Main.enhanceInputFiles()


    /**********************************************************************
     *  Enhances a classfile.
     *
     *  @param  in  The input stream of the classfile.
     *********************************************************************/

    private final void enhanceClassFile (InputStream in)
    {

        OutputStream out = null;
        try
        {
            File temp = File.createTempFile ("enhancer", ".class");
            out = new BufferedOutputStream (new FileOutputStream (temp));

            //enhance
            OutputStreamWrapper wrapper = new OutputStreamWrapper (out);
            boolean enhanced = this.enhancer.enhanceClassFile (in, wrapper);
            closeOutputStream (out);
            createOutputFile (enhanced, createClassFileName (wrapper.getClassName ()), temp);
        }
        catch (Throwable ex)
        {
            printError (null, ex);
        }
        finally
        {
            closeInputStream (in);
            closeOutputStream (out);
        }

    }  //Main.enhanceClassFile()


    /**********************************************************************
     *  Enhances a zipfile.
     *
     *  @param  name  The filename of the zipfile.
     *********************************************************************/

    private final void enhanceZipFile (String filename)
    {

        ZipInputStream  in = null;
        ZipOutputStream out = null;
        try
        {
            File temp = File.createTempFile ("enhancer", ".zip");
            in = new ZipInputStream (new BufferedInputStream (new FileInputStream (new File (filename))));
            out = new ZipOutputStream (new BufferedOutputStream (new FileOutputStream (temp)));

            //enhance the zipfile
            boolean enhanced = ByteCodeEnhancerHelper.enhanceZipFile (this.enhancer, in, out);
            closeOutputStream (out);
            out = null;

            //create the output file
            createOutputFile (enhanced, new File (filename).getName (), temp);
        }
        catch (Throwable ex)
        {
            printError (null, ex);
        }
        finally
        {
            closeOutputStream (out);
            closeInputStream (in);
        }

    }  //Main.enhanceZipFile()


    /**********************************************************************
     *  Opens an input stream for the given filename
     *
     *  @param  filename  The name of the file.
     *
     *  @return  The input stream.
     *
     *  @exception  FileNotFoundException  If the file could not be found.
     *********************************************************************/

    private static final InputStream openFileInputStream (String filename)
                                     throws FileNotFoundException
    {

     	return new BufferedInputStream (new FileInputStream (new File (filename)));

    }  //Main.openFileInputStream()


    /**********************************************************************
     *  Opens an input stream for the given classname. The input stream is
     *  created via an URL that is obtained by the current ClassLoader.
     *
     *  @param  classname  The name of the class (dot-notation).
     *
     *  @return  The iput stream.
     *
     *  @exception  IOException             If an I/O error occured.
     *  @exception  ClassNotFoundException  If the class could not be found.
     *********************************************************************/

    private final InputStream openClassInputStream (String classname)
                              throws IOException,
                                     ClassNotFoundException
    {

        URL url = Main.class.getClassLoader ().getSystemResource (createClassFileName (classname));
        if  (url == null)
        {
            throw new ClassNotFoundException (classname);
        }
        return url.openStream ();

    }  //Main.getClassInPath()


    /**********************************************************************
     *  Tests if a filename is a classfile name.
     *
     *  @param  filename  The name of the file.
     *
     *  @return  Do we have a potential classfile?
     *********************************************************************/

    private static final boolean isClassFileName (String filename)
    {

        return filename.endsWith (".class");

    }  //Main.isClassFileName()


    /**********************************************************************
     *  Tests if a filename is a zipfile (only by testing if the extension -
     *  ignoring the case - is <code>".zip"</code> or <code>".jar"</code>).
     *
     *  @param  filename  The name of the file.
     *
     *  @param  Do we have a potential zipfile?
     *********************************************************************/

    private static final boolean isZipFileName (String filename)
    {

        final int n = filename.length ();
        if  (n < 5)
        {
            return false;
        }
        String ext = filename.substring (n - 4);

        return ext.equalsIgnoreCase (".zip")  ||  ext.equalsIgnoreCase (".jar");

    }  //Main.isZipFileName


    /**********************************************************************
     *  Creates a filename from a classname (by replacing <code>'.'</code>
     *  by <code>'/'</code>.
     *
     *  @param  classname  The classname.
     *
     *  @return  The filename.
     *********************************************************************/

    private static final String createClassFileName (String classname)
    {

        return classname.replace ('.', '/') + ".class";

    }  //Main.createClassFileName()


    /**********************************************************************
     *  Creates a file object that represents the output zipfile for a given
     *  zipfile to enhance.
     *
     *  @param  zipfilename  The input zipfile name.
     *
     *  @return  The output zipfile name.
     *********************************************************************/

    private final File createZipOutputFile (String zipfilename)
    {

        return new File (this.cmdLineOpts.destinationDirectory, new File (zipfilename).getName ());

    }  //Main.createZipOutputFile()


    /**********************************************************************
     *  Creates the output file for an enhaced class- or zipfile. If the
     *  enhanced file is written back depends on the command line options.
     *
     *  @param  enhanced  Has the input file been enhanced?
     *  @param  filename  The name of the output file.
     *  @param  temp      The temp file, the output is written to.
     *
     *  @exception  IOException  If the file could not be created.
     *********************************************************************/

    private final void createOutputFile (boolean  enhanced,
                                         String   filename,
                                         File     temp)
                       throws IOException
    {

        //noWrite or (not enhanced and not forceWrite)
        if  (this.cmdLineOpts.noWrite  ||  ( ! enhanced  &&  ! this.cmdLineOpts.forceWrite))
        {
            temp.deleteOnExit ();
            return;
        }

        File file = new File (this.cmdLineOpts.destinationDirectory, filename);
        createPathOfFile (file);
        file.delete ();  //delete old file if exists
        //@olsen: added workaround to JDK bug with file.renameTo()
        boolean renamed = temp.renameTo(file);
        if (!renamed) {
            //@dave: empirical evidence shows that renameTo does not allow for
            // crossing filesystem boundaries.  If it fails, try "by hand".
            try {
                DataInputStream dis =
                    new DataInputStream(new FileInputStream(temp));
                DataOutputStream dos =
                    new DataOutputStream(new FileOutputStream(file));
                int PAGESIZE = 4096; // Suggest a better size?
                byte data[] = new byte[PAGESIZE];
                while (dis.available() > 0) {
                    int numRead = dis.read(data, 0, PAGESIZE);
                    dos.write(data, 0, numRead);
                }
                renamed = true;
                temp.delete ();  // delete temp file
            } catch (IOException ex) {
                // empty
            }
            if (!renamed) {
                throw new IOException("Could not rename temp file '" +
                                      temp.getAbsolutePath() +
                                      "' to '" + file.getAbsolutePath() + "'.");
            }
        }
    }


    /**********************************************************************
     *  Closes an input stream.
     *
     *  @param  in  The input stream.
     *********************************************************************/

    private final void closeInputStream (InputStream in)
    {

        if  (in != null)
        {
            try
            {
                in.close ();
            }
            catch (IOException ex)
            {
                printError (null, ex);
            }
        }

    }  //Main.closeInputStream()


    /**********************************************************************
     *  Closes an output stream.
     *
     *  @param  in  The output stream.
     *********************************************************************/

    private final void closeOutputStream (OutputStream out)
    {

        if  (out != null)
        {
            try
            {
                out.close ();
            }
            catch (IOException ex)
            {
                printError (null, ex);
            }
        }

    } //Main.closeOutputStream()


    /**********************************************************************
     *  Creates only the path of the given file.
     *
     *  @param  file  The file.
     *
     *  @exception  IOException  If an error occured.
     *********************************************************************/

    private static final void createPathOfFile (File file)
                              throws IOException
    {

        //Verzeichnis erzeugen
        File dir = file.getAbsoluteFile ().getParentFile ();
        if  ( ! dir.exists ()  &&  ! dir.mkdirs ())
        {
            throw new IOException ("Error creating directory '" + dir.getAbsolutePath () + "'.");
        }

    }  //Main.createPathOfFile()


    /**********************************************************************
     *  Prints out an error.
     *
     *  @param  msg  The error message (can be <code>null</code>).
     *  @param  ex   An optional exception (can be <code>null</code>).
     *********************************************************************/

    private final void printError (String    msg,
                                   Throwable ex)
    {

        if  (msg != null)
        {
            this.outErrors.println (msg + (ex != null  ?  ": " + ex  :  ""));
        }
        if (ex != null)
        {
            ex.printStackTrace (this.outErrors);
        }

    }  //Main.printError()


    /**********************************************************************
     *  Prints out a message.
     *
     *  @param  msg  The message.
     *********************************************************************/

    private final void printMessage (String msg)
    {

        this.outMessages.println (msg);

    }  //Main.printMessage()


    /**
     * Print a usage message to System.err
     */
    public static void usage() {
        //@olsen: document that main() takes a file name argument
        System.err.println("Usage: main <options> <file name>");
//@olsen: disabled feature
/*
        System.err.println("  { -copyclass | -cc }");
        System.err.println("  { -persistaware | -pa }");
        System.err.println("  { -persistcapable | -pc }");
*/
        System.err.println("  { -verbose | -v }");
        System.err.println("  { -force | -f }");
        System.err.println("  { -quiet | -q }");
//        System.err.println("  { -quietfield | -qf } <qualified field name>");
//        System.err.println("  { -quietclass | -qc } <qualified class name>");
//@olsen: disabled feature
/*
        System.err.println("  -inplace");
*/
//        System.err.println("  -noarrayopt");
//        System.err.println("  -nothisopt");
//        System.err.println("  -noinitializeropt");
//        System.err.println("  -noopt");
//@olsen: disabled feature
/*
        System.err.println("  -modifyjava");
*/
//@olsen: disabled feature
/*
        //@olsen: added missing  cmd line option
        System.err.println("  -modifyfinals");
*/
        System.err.println("  -nowrite");
        System.err.println(" -dest | -d <destination directory>");
//@olsen: disabled feature
/*
        System.err.println("  { -translatepackage | -tp } <orig pkg name> <new pkg name>");
*/
//@olsen: disabled feature
/*
        System.err.println("  { -arraydims | -a } <number of array dimensions>");
*/
//@olsen: disabled feature
/*
        System.err.println("  { -classpath | -cpath } <classpath for lookup>");
        System.err.println("  { -sysclasspath | -syscpath } <system classpath for lookup>");
*/
        //@olsen: new command line option for JDO meta data properties
        System.err.println("  --jdoProperties | -jp");
        System.err.println("  --timing | -t");

        System.err.println("  @<cmd-arg-file>");

        System.exit (1);
    }


    //#####################################################################
    /**
     *  A class for holding the command line options.
     */
    //#####################################################################

    private final class CmdLineOptions
    {


        /**
         *  The destination directory to write the enhanced classes to.
         */
        String destinationDirectory = null;


        /**
         *  Do timing messures?
         */
        boolean doTiming = false;


        /**
         *  Verbose output?
         */
        boolean verbose = false;


        /**
         *  No output at all?
         */
        boolean quiet = false;


        /**
         *  Force a write even if the classfile has not been enhanced?
         */
        boolean forceWrite = false;


        /**
         *  Really write the enhanced or not enhanced classes to disk?
         */
        boolean noWrite = false;


    }  //CmdLineOptions


}  //Main