/*
 * 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.generator;

import java.lang.reflect.Modifier;

import java.util.Iterator;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;

import java.io.Serializable;
import java.io.File;
import java.io.Writer;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;

import com.sun.jdo.api.persistence.enhancer.util.Assertion;

import com.sun.jdo.api.persistence.enhancer.meta.ExtendedJDOMetaData;
import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataPropertyImpl;

import com.sun.jdo.spi.persistence.utility.generator.JavaFileWriter;
import com.sun.jdo.spi.persistence.utility.generator.JavaClassWriter;
import com.sun.jdo.spi.persistence.utility.generator.JavaClassWriterHelper;
import com.sun.jdo.spi.persistence.utility.generator.io.IOJavaFileWriter;
import com.sun.jdo.spi.persistence.utility.generator.io.IOJavaClassWriter;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import com.sun.jdo.api.persistence.enhancer.LogHelperEnhancer;
import java.util.ResourceBundle;
import com.sun.jdo.spi.persistence.utility.I18NHelper;


/**
 *
 */
public final class Main
    extends Assertion
{
    //
    // CAUTION: THE ENHANCER-GENERATOR DEALS WITH CLASS NAMES IN THE
    // JVM FORMAT, THAT IS, '/' INSTEAD OF '.' IS USED AS SEPARATOR.
    //
    // Class names in the Java language format ('.' as separator) are
    // referred to as "normalized" in the generator code.
    //
    // File names are named as such; they use File.separatorChar as
    // separator.
    //

     /** The logger */
    private static final Logger logger = LogHelperEnhancer.getLogger();
    private static final String dotLine =
        "----------------------------------------------------------------------";
    /**
     *  The stream to write messages to.
     */
    private final PrintWriter out = new PrintWriter(System.out, true);

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

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

    /**
     * Java Writer Class
     */
    private JavaFileWriter fWriter = null;
    private JavaClassWriter writer = null;

    /**
     * The MetaData for generating classes.
     */
    private ExtendedJDOMetaData meta = null;

    private File destinationDir = null;


    /**
     * I18N message handler
     */
    private final static ResourceBundle messages = I18NHelper.loadBundle("com.sun.jdo.api.persistence.enhancer.Bundle"); // NOI18N
    public Main()
    {}

    public Main(ExtendedJDOMetaData meta, File destinationDir)
             throws IOException {
        this.meta = meta;
        this.destinationDir = destinationDir;
        createDestinationDir();
    }

    /**
     *
     */
    public static final void main(String[] argv)
    {
        final Main gen = new Main();
        try {
            gen.opts.processArgs(argv);
            gen.init();
            gen.generate();
        } catch(Exception ex) {
            gen.printError(null, ex);
        }
    }

    /**
     *  A class for holding the command line options.
     */
    private class CmdLineOptions
    {
        // final Collection inputFileNames = new ArrayList();
        String destinationDirectory = null;
        String jdoXMLModelFileName = null;
        String jdoPropertiesFileName = null;
        boolean verbose = false;

        /**
         * Print a usage message to System.err
         */
        public void usage() {
            err.println("Usage: Main <options> <arguments>...");
            err.println("Options:");
            err.println("  -v, --verbose            print verbose output");
            err.println("  -d, --dest <dir>         destination directory for output files");
            err.println("  -p, --properties <file>  use property file for meta data");
            err.println();
            err.println("Arguments:");
            err.println();
            err.println("Returns a non-zero value in case of errors.");
            System.exit(1);
        }

        /**
         * Process command line options
         */
        protected int processArgs(String[] argv)
        {
            for (int i = 0; i < argv.length; i++) {
                final String arg = argv[i];
                if (arg.equals("-v")
                    || arg.equals("--verbose")) {
                    verbose = true;
                    continue;
                }
                if (arg.equals("-d")
                    || arg.equals("--dest")) {
                    if (argv.length - i < 2) {
                        printError("Missing argument to the -d/-dest option", null);
                        usage();
                    }
                    destinationDirectory = argv[++i];
                    continue;
                }
                if (arg.equals("-p") ||
                    arg.equals("--properties")) {
                    if (argv.length - i < 2) {
                        printError("Missing argument to the -p/--properties option", null);
                        usage();
                    }
                    jdoPropertiesFileName = argv[++i];
                    continue;
                }
                if (arg.length() > 0 && arg.charAt(0) == '-') {
                    printError("Unrecognized option:" + arg, null);
                    usage();
                }
                if (arg.length() == 0) {
                    printMessage("Ignoring empty command line argument.");
                    continue;
                }

                //inputFileNames.add(arg);
            }

            // The user must specify a destination directory
            if (jdoPropertiesFileName == null) {
                printError("No destination directory specified", null);
                usage();
            }

            // The user must specify a destination directory
            if (destinationDirectory == null) {
                printError("No destination directory specified", null);
                usage();
            }

            return 0;
        }
    }

    private void init()
        throws FileNotFoundException, IOException
    {
        // load the properties
        affirm(opts.jdoPropertiesFileName != null);
        FileInputStream finput = null;
        try {
            final File f = new File(opts.jdoPropertiesFileName);
            finput = new FileInputStream(f);
            final Properties props = new Properties();
            props.load(finput);
            meta = new JDOMetaDataPropertyImpl(props, out);
        } finally {
            if (finput != null) {
                try {
                    finput.close();
                } catch(Exception ex) {
                    printError(ex.getMessage(), ex);
                }
            }
        }

        affirm(opts.destinationDirectory != null);
        destinationDir = new File(opts.destinationDirectory);
        createDestinationDir();
    }

    private void createDestinationDir()
        throws IOException
    {
        // create the destination directory
        if (!destinationDir.exists() && !destinationDir.mkdirs()) {
            throw new IOException(I18NHelper.getMessage(messages,"EXC_DestDirCreateFailure",destinationDir));  //NOI18N
        }
    }

    private void generate()
        throws IOException
    {
        final String[] classes = meta.getKnownClasses();
        for (int i = 0; i < classes.length; i++) {
            final String className = classes[i];
            generate(className);
        }
    }

    // entry point for EJB TP class generation
    // The argument is a fully qualified class name expected in the
    // JVM format, that is, with '/' for '.' as separator.  See comment
    // at the beginning of this class.
    public File generate(final String className)
        throws IOException
    {
        affirm(className != null);
        printMessage("generating '" + className + "'...");

        //@olsen, 4653156: fixed file name
        final String filePath = className.replace('/', File.separatorChar);
        final String classFileName
            = filePath + JavaClassWriterHelper.javaExtension_;
        final File file = new File(destinationDir, classFileName);

        //@olsen: not needed: IOJavaFileWriter takes care of creating file
        // create the destination directory
        //final File dir = file.getAbsoluteFile().getParentFile();
        //if (!dir.exists() && !dir.mkdirs()) {
        //    throw new IOException("unable to create destination directory: "
        //                          + "'" + destinationDir + "'");
        //}

        fWriter = new IOJavaFileWriter(file);
        writer = new IOJavaClassWriter();
        generateClass(className);
        fWriter.addClass(writer);
        printMessage("DONE generating '" + className + "'...");

        //@olsen: moved from finally{} to main block
        // by JavaFileWriter, no stale resources remain allocated ever
        fWriter.save();
        return file;
    }

    private void generateClass(final String className)
        throws IOException
    {
        affirm(className != null);

        final String packageName = ImplHelper.getPackageName(className);
        fWriter.setPackage(packageName, null);

        // write the class header and key class
        final String oidClassName = meta.getKeyClass(className);
        if (oidClassName == null) {
            writeClassHeader(className);
        } else {
            final String oidPackageName
                = ImplHelper.getPackageName(oidClassName);
            affirm(packageName.equals(oidPackageName),
                   "PC class and key class must be in same package.");

            final boolean enclosedOid
                = oidClassName.startsWith(className + "$");
            if (enclosedOid) {
                writeClassHeader(className);
                writeOidClass(className, ImplHelper.getClassName(oidClassName),
                    enclosedOid);
            } else {
                writeOidClass(className, ImplHelper.getClassName(oidClassName),
                    enclosedOid);
                writeClassHeader(className);
            }
        }

        writeClassMembers(className);

        // write the augmentation
        final boolean isPC = meta.isPersistenceCapableClass(className);
        if (isPC) {
            final boolean isPCRoot
                = meta.isPersistenceCapableRootClass(className);
            if (isPCRoot) {
                writePCRootMembers(className);
            }
            writePCMembers(className);
        }
    }

    private void writeClassHeader(final String className)
        throws IOException
    {
        final boolean isPCRoot = meta.isPersistenceCapableRootClass(className);
        final String superclass =
            ImplHelper.normalizeClassName(meta.getSuperClass(className));

        final String[] comments = null;
        final String[] interfaces
            = (isPCRoot
               ? new String[]{ ImplHelper.CLASSNAME_JDO_PERSISTENCE_CAPABLE, "Cloneable" }
               : null);
        writer.setClassDeclaration(meta.getClassModifiers(className),
                ImplHelper.getClassName(className), comments);
        writer.setSuperclass(superclass);
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                writer.addInterface(interfaces[i]);
            }
        }
    }

    private void writeClassMembers(final String className)
        throws IOException
    {
        final String[] comments = new String[]{
            dotLine,
            "Class Members:",
            dotLine
        };

        // write default constructor
        writer.addConstructor(ImplHelper.getClassName(className),
            Modifier.PUBLIC, null, null, null,
            ImplHelper.getDefaultConstructorImpl(),
            comments);

        final String[] fieldNames = meta.getKnownFields(className);
        final int n = (fieldNames != null ? fieldNames.length : 0);

        // write method clone() for enhancer testing purpose
        final ArrayList list = new ArrayList();
        for (int i = 0; i < n; i++) {
            final String fieldName = (String)fieldNames[i];
            final int access = meta.getFieldModifiers(className, fieldName);
            if ((access & Modifier.STATIC) == 0) {
                list.add(fieldName);
            }
        }

        writer.addMethod(
            "clone",
            Modifier.PUBLIC,
            JavaClassWriterHelper.Object_,
            null,
            null,
            new String[]{ "java.lang.CloneNotSupportedException" },
            ImplHelper.getCloneImpl(className),
            ImplHelper.COMMENT_NOT_ENHANCER_ADDED);

        // write the fields and with their bean getters/setters
        for (int i = 0; i < n; i++) {
            final String fieldName = (String)fieldNames[i];
            writeFieldMember(className, fieldName);
        }
    }

    private void writeFieldMember(final String className,
                                  final String fieldName)
        throws IOException
    {
        final String fieldType = meta.getFieldType(className, fieldName);
        final String normalizedFieldType =
            ImplHelper.normalizeClassName(fieldType);
        final int fieldNumber = meta.getFieldNo(className, fieldName);
        final int flags = meta.getFieldFlags(className, fieldName);

        final int access = meta.getFieldModifiers(className, fieldName);

        // the field
        writer.addField(
            fieldName,
            access,
            normalizedFieldType,
            null, null);

        // do not write bean getters and setters for static fields
        if ((access & Modifier.STATIC) != 0) {
            return;
        }

        // accessor
        {
            affirm(((flags & meta.CHECK_READ) == 0)
                   | (flags & meta.MEDIATE_READ) == 0);
            final String[] impl;
            if ((flags & meta.CHECK_READ) != 0) {
                impl = ImplHelper.getFieldCheckReadImpl(fieldName,
                                                        fieldType,
                                                        fieldNumber);
            } else if ((flags & meta.MEDIATE_READ) != 0) {
                impl = ImplHelper.getFieldMediateReadImpl(fieldName,
                                                          fieldType,
                                                          fieldNumber);
            } else {
                impl = ImplHelper.getFieldDirectReadImpl(fieldName,
                                                         fieldType,
                                                         fieldNumber);
            }
            writer.addMethod(
                createMethodName(JavaClassWriterHelper.get_, fieldName),
                Modifier.PUBLIC,
                normalizedFieldType,
                null, null, null,
                impl,
                ImplHelper.COMMENT_ENHANCER_ADDED);
        }

        // mutator
        {
            affirm(((flags & meta.CHECK_WRITE) == 0)
                   | (flags & meta.MEDIATE_WRITE) == 0);
            final String[] impl;
            if ((flags & meta.CHECK_WRITE) != 0) {
                impl = ImplHelper.getFieldCheckWriteImpl(fieldName,
                                                         fieldType,
                                                         fieldNumber,
                                                         fieldName);
            } else if ((flags & meta.MEDIATE_WRITE) != 0
                    && !meta.isKnownNonManagedField(className, fieldName,
                    null)) {
                impl = ImplHelper.getFieldMediateWriteImpl(fieldName,
                                                           fieldType,
                                                           fieldNumber,
                                                           fieldName);
            } else {
                impl = ImplHelper.getFieldDirectWriteImpl(fieldName,
                                                          fieldType,
                                                          fieldNumber,
                                                          fieldName);
            }
            writer.addMethod(
                createMethodName(JavaClassWriterHelper.set_, fieldName),
                Modifier.PUBLIC,
                JavaClassWriterHelper.void_,
                new String[]{ fieldName },
                new String[]{ normalizedFieldType },
                null,
                impl,
                ImplHelper.COMMENT_ENHANCER_ADDED);
        }

    }

    private void writePCRootMembers(final String className)
        throws IOException
    {
        final String[] comments = new String[]{
            dotLine,
            "Augmentation for Persistence-Capable Root Classes (added by enhancer):",
            dotLine
        };

        // write constructor with parameter StateManager
        writer.addConstructor(
            ImplHelper.getClassName(className),
            Modifier.PUBLIC,
            new String[]{ ImplHelper.FIELDNAME_JDO_STATE_MANAGER },
            new String[]{ ImplHelper.CLASSNAME_JDO_STATE_MANAGER },
            null,
            ImplHelper.getJDOConstructorSMImpl(ImplHelper.FIELDNAME_JDO_STATE_MANAGER),
            comments);

        // jdoStateManager
        writer.addField(
            ImplHelper.FIELDNAME_JDO_STATE_MANAGER,
            Modifier.PUBLIC | Modifier.TRANSIENT,
            ImplHelper.CLASSNAME_JDO_STATE_MANAGER,
            JavaClassWriterHelper.null_,
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // jdoFlags
        writer.addField(
            ImplHelper.FIELDNAME_JDO_FLAGS,
            Modifier.PUBLIC | Modifier.TRANSIENT,
            "byte",
            "0",
            ImplHelper.COMMENT_ENHANCER_ADDED);


        // jdoGetStateManager
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_GET_STATE_MANAGER,
            Modifier.PUBLIC | Modifier.FINAL,
            ImplHelper.CLASSNAME_JDO_STATE_MANAGER, null, null, null,
            new String[] { "return " +  ImplHelper.FIELDNAME_JDO_STATE_MANAGER + JavaClassWriterHelper.delim_ },
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // jdoSetStateManager
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_SET_STATE_MANAGER,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.void_,
            new String[] { ImplHelper.FIELDNAME_JDO_STATE_MANAGER },
            new String[] { ImplHelper.CLASSNAME_JDO_STATE_MANAGER }, null,
            new String[] { "this." +  ImplHelper.FIELDNAME_JDO_STATE_MANAGER + " = " + ImplHelper.FIELDNAME_JDO_STATE_MANAGER + JavaClassWriterHelper.delim_ },
            ImplHelper.COMMENT_ENHANCER_ADDED);

        //jdoGetFlags
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_GET_FLAGS,
            Modifier.PUBLIC | Modifier.FINAL,
            "byte", null, null, null,
            new String[] { "return " +  ImplHelper.FIELDNAME_JDO_FLAGS +
                    JavaClassWriterHelper.delim_ },
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // jdoSetFlags
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_SET_FLAGS,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.void_,
            new String[] { ImplHelper.FIELDNAME_JDO_FLAGS },
            new String[] { "byte" }, null,
            new String[] { "this." +  ImplHelper.FIELDNAME_JDO_FLAGS + " = " + ImplHelper.FIELDNAME_JDO_FLAGS + JavaClassWriterHelper.delim_ },
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // getPersistenceManager
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_GET_PERSISTENCE_MANAGER,
            Modifier.PUBLIC | Modifier.FINAL,
            ImplHelper.CLASSNAME_JDO_PERSISTENCE_MANAGER, null, null, null,
            ImplHelper.getJDOStateManagerObjectDelegationImpl("getPersistenceManager()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // getObjectId
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_GET_OBJECT_ID,
            Modifier.PUBLIC | Modifier.FINAL,
            Object.class.getName(), null, null, null,
            ImplHelper.getJDOStateManagerObjectDelegationImpl("getObjectId()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // is-methods
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_IS_PERSISTENT,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.boolean_, null, null, null,
            ImplHelper.getJDOStateManagerBooleanDelegationImpl("isPersistent()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        writer.addMethod(
            ImplHelper.METHODNAME_JDO_IS_TRANSACTIONAL,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.boolean_, null, null, null,
            ImplHelper.getJDOStateManagerBooleanDelegationImpl("isTransactional()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        writer.addMethod(
            ImplHelper.METHODNAME_JDO_IS_NEW,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.boolean_, null, null, null,
            ImplHelper.getJDOStateManagerBooleanDelegationImpl("isNew()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        writer.addMethod(
            ImplHelper.METHODNAME_JDO_IS_DELETED,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.boolean_, null, null, null,
            ImplHelper.getJDOStateManagerBooleanDelegationImpl("isDeleted()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        writer.addMethod(
            ImplHelper.METHODNAME_JDO_IS_DIRTY,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.boolean_, null, null, null,
            ImplHelper.getJDOStateManagerBooleanDelegationImpl("isDirty()"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // makeDirty
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_MAKE_DIRTY,
            Modifier.PUBLIC | Modifier.FINAL,
            JavaClassWriterHelper.void_,
            new String[]{ "fieldName" },
            new String[]{ String.class.getName() },
            null,
            ImplHelper.getJDOStateManagerVoidDelegationImpl("makeDirty(fieldName)"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

    }

    private void writePCMembers(final String className)
        throws IOException
    {
        final String[] comments = new String[]{
            dotLine,
            "Augmentation for Persistence-Capable Classes (added by enhancer):",
            dotLine
        };

        final String[] managedFieldNames
            = meta.getManagedFields(className);
        final String[] managedFieldTypes
            = meta.getFieldType(className, managedFieldNames);
        final boolean isPCRoot
            = meta.isPersistenceCapableRootClass(className);

        // jdoGetField
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_GET_FIELD,
            Modifier.PUBLIC,
            JavaClassWriterHelper.Object_,
            new String[]{ "fieldNumber" },
            new String[]{ "int" },
            null,
            ImplHelper.getJDOGetFieldImpl("fieldNumber",
                                          managedFieldNames,
                                          managedFieldTypes),
            comments);

        // jdoSetField
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_SET_FIELD,
            Modifier.PUBLIC,
            JavaClassWriterHelper.void_,
            new String[]{ "fieldNumber", "obj" },
            new String[]{ "int", JavaClassWriterHelper.Object_ },
            null,
            ImplHelper.getJDOSetFieldImpl("fieldNumber", "obj",
                                          managedFieldNames,
                                          managedFieldTypes),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // jdoClear
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_CLEAR,
            Modifier.PUBLIC,
            JavaClassWriterHelper.void_, null, null, null,
            ImplHelper.getJDOClearImpl(className, meta, managedFieldNames,
                                       managedFieldTypes),
            ImplHelper.COMMENT_ENHANCER_ADDED);

        // jdoNewInstance
        writer.addMethod(
            ImplHelper.METHODNAME_JDO_NEW_INSTANCE,
            Modifier.PUBLIC,
            JavaClassWriterHelper.Object_,
            new String[]{ "sm" },
            new String[]{ ImplHelper.CLASSNAME_JDO_STATE_MANAGER },
            null,
            ImplHelper.getJDONewInstanceImpl(className, "sm"),
            ImplHelper.COMMENT_ENHANCER_ADDED);

    }

    private void writeOidClass(final String className,
                               final String oidClassName,
                               final boolean enclosedOid)
        throws IOException
    {
        final String[] comments = new String[]{
            dotLine,
            "Key Class:",
            dotLine
        };

        final String superOidClassName
            = ImplHelper.normalizeClassName(meta.getSuperKeyClass(className));

        JavaClassWriter oidWriter = new IOJavaClassWriter();

        oidWriter.setClassDeclaration(
            (enclosedOid ? Modifier.PUBLIC | Modifier.STATIC : 0),
            oidClassName,
            ImplHelper.COMMENT_NOT_ENHANCER_ADDED);
        oidWriter.setSuperclass(superOidClassName);
        oidWriter.addInterface(Serializable.class.getName());

        final boolean isPCRoot
            = meta.isPersistenceCapableRootClass(className);

        final String[] pknames = meta.getKeyFields(className);
        final String[] pktypes = meta.getFieldType(className, pknames);

        // write the PK-fields
        for (int i = 0; i < pknames.length; i++) {
            oidWriter.addField(
                pknames[i],
                Modifier.PUBLIC,
                ImplHelper.normalizeClassName(pktypes[i]),
                null,
                null);
        }

        // write default constructor
        oidWriter.addConstructor(
            oidClassName,
            Modifier.PUBLIC,
            null, null, null,
            ImplHelper.getDefaultConstructorImpl(),
            ImplHelper.COMMENT_NOT_ENHANCER_ADDED);

        // hashCode
        oidWriter.addMethod(
            "hashCode",
            Modifier.PUBLIC,
            "int",
            null,
            null,
            null,
            ImplHelper.getOidHashCodeImpl(pknames,
                                          pktypes,
                                          isPCRoot),
            ImplHelper.COMMENT_NOT_ENHANCER_ADDED);

        // equals
        oidWriter.addMethod(
            "equals", Modifier.PUBLIC, JavaClassWriterHelper.boolean_,
            new String[]{ "pk" },
            new String[]{ Object.class.getName() },
            null,
            ImplHelper.getOidEqualsImpl(oidClassName,
                                        pknames,
                                        pktypes,
                                        "pk",
                                        isPCRoot),
            ImplHelper.COMMENT_NOT_ENHANCER_ADDED);

          if (enclosedOid) {
              writer.addClass(oidWriter);
          } else {
              fWriter.addClass(oidWriter);
          }
    }

    static private String createMethodName(final String prefix,
                                           final String fieldName)
    {
        return (prefix + Character.toUpperCase(fieldName.charAt(0))
                + fieldName.substring(1));
    }

    //XXX use common logger later
    private void printMessage(String msg)
    {
	  logger.finest("TP PCClassGen: " + msg); // NOI18N
    }

    private void printError(String msg, Throwable ex)
    {
        if (msg != null) {
            String errmsg=msg + (ex != null ? ": " + ex.getMessage() : ""); //NOI18N
            logger.log(Logger.SEVERE,"CME.generic_exception",errmsg); //NOI18N
        }
        if (ex != null) {
            logger.log(Logger.SEVERE,"CME.generic_exception_stack",ex); //NOI18N
        }
    }
}
