/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.jdbc.meta;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.ApplicationIdTool;
import org.apache.openjpa.enhance.CodeGenerator;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedBlobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedClobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.PrimitiveFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationCollectionInverseKeyFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationCollectionTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.StateComparisonVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.StringFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.SubclassJoinDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGenerator;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.SchemaParser;
import org.apache.openjpa.jdbc.schema.Schemas;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.schema.XMLSchemaParser;
import org.apache.openjpa.jdbc.sql.SQLExceptions;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.CodeFormat;
import org.apache.openjpa.lib.util.Files;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataFactory;
import org.apache.openjpa.meta.MetaDataModes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.NoneMetaDataFactory;
import org.apache.openjpa.meta.QueryMetaData;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.asm.ClassNodeTracker;
import org.apache.openjpa.util.asm.EnhancementClassLoader;
import org.apache.openjpa.util.asm.EnhancementProject;
import org.apache.xbean.asm9.Opcodes;
import org.apache.xbean.asm9.Type;
import org.apache.xbean.asm9.tree.ClassNode;
import org.apache.xbean.asm9.tree.InsnNode;
import org.apache.xbean.asm9.tree.MethodInsnNode;
import org.apache.xbean.asm9.tree.MethodNode;
import org.apache.xbean.asm9.tree.VarInsnNode;

/**
 * Reverse-maps a schema into class mappings and the associated java
 * code. Generates a Java code files for persistent classes and associated
 * identity classes and metadata.
 *
 * @author Abe White
 */
public class ReverseMappingTool implements MetaDataModes, Cloneable {

    /**
     * Unmapped table.
     */
    public static final int TABLE_NONE = 0;

    /**
     * Primary table for a new base class.
     */
    public static final int TABLE_BASE = 1;

    /**
     * Secondary table of an existing class. There is exactly one row in
     * this table for each row in the primary table.
     */
    public static final int TABLE_SECONDARY = 2;

    /**
     * Secondary table of an existing class. There is zero or one row in
     * this table for each row in the primary table.
     */
    public static final int TABLE_SECONDARY_OUTER = 3;

    /**
     * Association table.
     */
    public static final int TABLE_ASSOCIATION = 4;

    /**
     * Subclass table.
     */
    public static final int TABLE_SUBCLASS = 5;

    public static final String LEVEL_NONE = "none";
    public static final String LEVEL_PACKAGE = "package";
    public static final String LEVEL_CLASS = "class";

    /**
     * Access type for generated source, defaults to field-based access.
     */
    public static final String ACCESS_TYPE_FIELD = "field";
    public static final String ACCESS_TYPE_PROPERTY = "property";

    private static Localizer _loc = Localizer.forPackage
        (ReverseMappingTool.class);

    // map java keywords to related words to use in their place
    private static final Map _javaKeywords = new HashMap();

    static {
        InputStream in = ReverseMappingTool.class.getResourceAsStream
            ("java-keywords.rsrc");
        try {
            String[] keywords = StringUtil.split(new BufferedReader
                (new InputStreamReader(in)).readLine(), ",", 0);

            for (int i = 0; i < keywords.length; i += 2)
                _javaKeywords.put(keywords[i], keywords[i + 1]);
        } catch (IOException ioe) {
            throw new InternalException(ioe);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
            }
        }
    }

    private final JDBCConfiguration _conf;
    private final Log _log;
    private final Map _tables = new HashMap();
    private final EnhancementProject _project = new EnhancementProject();
    private final EnhancementClassLoader _loader = new EnhancementClassLoader(_project);
    private StrategyInstaller _strat = null;
    private String _package = null;
    private File _dir = null;
    private MappingRepository _repos = null;
    private SchemaGroup _schema = null;
    private boolean _nullAsObj = false;
    private boolean _blobAsObj = false;
    private boolean _useGenericColl = false;
    private Properties _typeMap = null;
    private boolean _useFK = false;
    private boolean _useSchema = false;
    private boolean _pkOnJoin = false;
    private boolean _datastore = false;
    private boolean _builtin = true;
    private boolean _inner = false;
    private String _idSuffix = "Id";
    private boolean _inverse = true;
    private boolean _detachable = false;
    private boolean _genAnnotations = false;
    private String _accessType = ACCESS_TYPE_FIELD;
    private CodeFormat _format = null;
    private ReverseCustomizer _custom = null;
    private String _discStrat = null;
    private String _versStrat = null;
    private boolean _useSchemaElement = true;

    // we have to track field names that were created but then abandoned by
    // the customizer so that we don't attempt to use them again; doing so can
    // mess up certain customizers (bug 881)
    private Set _abandonedFieldNames = null;

    // generated annotations, key = metadata, val = list of annotations
    private Map _annos = null;

    /**
     * Constructor. Supply configuration.
     */
    public ReverseMappingTool(JDBCConfiguration conf) {
        _conf = conf;
        _log = conf.getLog(OpenJPAConfiguration.LOG_METADATA);
    }

    /**
     * Return the internal strategy installer.
     */
    private StrategyInstaller getStrategyInstaller() {
        if (_strat == null)
            _strat = new ReverseStrategyInstaller(getRepository());
        return _strat;
    }

    /**
     * Return the configuration provided on construction.
     */
    public JDBCConfiguration getConfiguration() {
        return _conf;
    }

    /**
     * Return the log to write to.
     */
    public Log getLog() {
        return _log;
    }

    /**
     * Return the default package for the generated classes, or null if unset.
     */
    public String getPackageName() {
        return _package;
    }

    /**
     * Set the default package for the generated classes; use null to
     * indicate no package.
     */
    public void setPackageName(String packageName) {
        _package = StringUtil.trimToNull(packageName);
    }

    /**
     * The file to output the generated code to, or null for
     * the current directory. If the directory matches the package, it will
     * be used. Otherwise, the package structure will be created under
     * this directory.
     */
    public File getDirectory() {
        return _dir;
    }

    /**
     * The file to output the generated code to, or null for
     * the current directory. If the directory matches the package, it will
     * be used. Otherwise, the package structure will be created under
     * this directory.
     */
    public void setDirectory(File dir) {
        _dir = dir;
    }

    /**
     * Return true if the schema name will be included in the generated
     * class name for each table. Defaults to false.
     */
    public boolean getUseSchemaName() {
        return _useSchema;
    }

    /**
     * Set whether the schema name will be included in the generated
     * class name for each table. Defaults to false.
     */
    public void setUseSchemaName(boolean useSchema) {
        _useSchema = useSchema;
    }

    /**
     * Return whether the foreign key name will be used to generate
     * relation names. Defaults to false.
     */
    public boolean getUseForeignKeyName() {
        return _useFK;
    }

    /**
     * Set whether the foreign key name will be used to generate
     * relation names. Defaults to false.
     */
    public void setUseForeignKeyName(boolean useFK) {
        _useFK = useFK;
    }

    /**
     * Return whether even nullable columns will be mapped to wrappers
     * rather than primitives. Defaults to false.
     */
    public boolean getNullableAsObject() {
        return _nullAsObj;
    }

    /**
     * Set whether even nullable columns will be mapped to wrappers
     * rather than primitives. Defaults to false.
     */
    public void setNullableAsObject(boolean nullAsObj) {
        _nullAsObj = nullAsObj;
    }

    /**
     * Whether to reverse-map blob columns as containing serialized Java
     * objects, rather than the default of using a byte[] field.
     */
    public boolean getBlobAsObject() {
        return _blobAsObj;
    }

    /**
     * Whether to reverse-map blob columns as containing serialized Java
     * objects, rather than the default of using a byte[] field.
     */
    public void setBlobAsObject(boolean blobAsObj) {
        _blobAsObj = blobAsObj;
    }

    /**
     * Whether to use generic collections on one-to-many and many-to-many
     * relations instead of untyped collections.
     */
    public boolean getUseGenericCollections() {
        return _useGenericColl;
    }

    /**
     * Whether to use generic collections on one-to-many and many-to-many
     * relations instead of untyped collections.
     */
    public void setUseGenericCollections(boolean useGenericCollections) {
        _useGenericColl = useGenericCollections;
    }

    /**
     * Map of JDBC-name to Java-type-name entries that allows customization
     * of reverse mapping columns to field types.
     */
    public Properties getTypeMap() {
        return _typeMap;
    }

    /**
     * Map of JDBC-name to Java-type-name entries that allows customization
     * of reverse mapping columns to field types.
     */
    public void setTypeMap(Properties typeMap) {
        _typeMap = typeMap;
    }

    /**
     * Return true if join tables are allowed to have primary keys, false
     * if all primary key tables will be mapped as persistent classes.
     * Defaults to false.
     */
    public boolean getPrimaryKeyOnJoin() {
        return _pkOnJoin;
    }

    /**
     * Set to true if join tables are allowed to have primary keys, false
     * if all primary key tables will be mapped as persistent classes.
     * Defaults to false.
     */
    public void setPrimaryKeyOnJoin(boolean pkOnJoin) {
        _pkOnJoin = pkOnJoin;
    }

    /**
     * Whether to use datastore identity when possible. Defaults to false.
     */
    public boolean getUseDataStoreIdentity() {
        return _datastore;
    }

    /**
     * Whether to use datastore identity when possible. Defaults to false.
     */
    public void setUseDataStoreIdentity(boolean datastore) {
        _datastore = datastore;
    }

    /**
     * Whether to use built in identity classes when possible. Defaults to true.
     */
    public boolean getUseBuiltinIdentityClass() {
        return _builtin;
    }

    /**
     * Whether to use built in identity classes when possible. Defaults to true.
     */
    public void setUseBuiltinIdentityClass(boolean builtin) {
        _builtin = builtin;
    }

    /**
     * Whether or not to generate inner classes when creating application
     * identity classes.
     */
    public boolean getInnerIdentityClasses() {
        return _inner;
    }

    /**
     * Whether or not to generate inner classes when creating application
     * identity classes.
     */
    public void setInnerIdentityClasses(boolean inner) {
        _inner = inner;
    }

    /**
     * Suffix used to create application identity class name from a class name,
     * or in the case of inner identity classes, the inner class name.
     */
    public String getIdentityClassSuffix() {
        return _idSuffix;
    }

    /**
     * Suffix used to create application identity class name from a class name,
     * or in the case of inner identity classes, the inner class name.
     */
    public void setIdentityClassSuffix(String suffix) {
        _idSuffix = suffix;
    }

    /**
     * Whether to generate inverse 1-many/1-1 relations for all many-1/1-1
     * relations. Defaults to true.
     */
    public boolean getInverseRelations() {
        return _inverse;
    }

    /**
     * Whether to generate inverse 1-many/1-1 relations for all many-1/1-1
     * relations. Defaults to true.
     */
    public void setInverseRelations(boolean inverse) {
        _inverse = inverse;
    }

    /**
     * Whether to make generated classes detachable. Defaults to false.
     */
    public boolean getDetachable() {
        return _detachable;
    }

    /**
     * Whether to make generated classes detachable. Defaults to false.
     */
    public void setDetachable(boolean detachable) {
        _detachable = detachable;
    }

    /**
     * Default discriminator strategy for base class mappings.
     */
    public String getDiscriminatorStrategy() {
        return _discStrat;
    }

    /**
     * Default discriminator strategy for base class mappings.
     */
    public void setDiscriminatorStrategy(String discStrat) {
        _discStrat = discStrat;
    }

    /**
     * Default version strategy for base class mappings.
     */
    public String getVersionStrategy() {
        return _versStrat;
    }

    /**
     * Default version strategy for base class mappings.
     */
    public void setVersionStrategy(String versionStrat) {
        _versStrat = versionStrat;
    }

    /**
     * Whether to generate annotations along with generated code. Defaults
     * to false.
     */
    public boolean getGenerateAnnotations() {
        return _genAnnotations;
    }

    /**
     * Whether to generate annotations along with generated code. Defaults
     * to false.
     */
    public void setGenerateAnnotations(boolean genAnnotations) {
        _genAnnotations = genAnnotations;
    }

    /**
     * Whether to use field or property-based access on generated code.
     * Defaults to field-based access.
     */
    public String getAccessType() {
        return _accessType;
    }

    /**
     * Whether to use field or property-based access on generated code.
     * Defaults to field-based access.
     */
    public void setAccessType(String accessType) {
        this._accessType = ACCESS_TYPE_PROPERTY.equalsIgnoreCase(accessType) ?
            ACCESS_TYPE_PROPERTY : ACCESS_TYPE_FIELD;
    }

    /**
     * The code formatter for the generated Java code.
     */
    public CodeFormat getCodeFormat() {
        return _format;
    }

    /**
     * Set the code formatter for the generated Java code.
     */
    public void setCodeFormat(CodeFormat format) {
        _format = format;
    }

    /**
     * Return the customizer in use, or null if none.
     */
    public ReverseCustomizer getCustomizer() {
        return _custom;
    }

    /**
     * Set the customizer. The configuration on the customizer, if any,
     * should already be set.
     */
    public void setCustomizer(ReverseCustomizer customizer) {
        if (customizer != null)
            customizer.setTool(this);
        _custom = customizer;
    }

    /**
     * Returns whether or not the schema name will be included in the @Table
     * annotation within the generated class for each table, as well as the
     * corresponding XML mapping files. The initialized value is true (in order
     * to preserve backwards compatibility).
     */
    public boolean getUseSchemaElement() {
    	return _useSchemaElement;
    }

    /**
     * Sets whether or not the schema name will be included in the @Table
     * annotation within the generated class for each table, as well as the
     * corresponding XML mapping files.
     */
    public void setUseSchemaElement(boolean useSchemaElement) {
    	_useSchemaElement = useSchemaElement;
    }

    /**
     * Return the mapping repository used to hold generated mappings. You
     * can also use the repository to seed the schema group to generate
     * classes from.
     */
    public MappingRepository getRepository() {
        if (_repos == null) {
            // create empty repository
            _repos = _conf.newMappingRepositoryInstance();
            _repos.setMetaDataFactory(NoneMetaDataFactory.getInstance());
            _repos.setMappingDefaults(NoneMappingDefaults.getInstance());
            _repos.setResolve(MODE_NONE);
            _repos.setValidate(MetaDataRepository.VALIDATE_NONE);
        }
        return _repos;
    }

    /**
     * Set the repository to use.
     */
    public void setRepository(MappingRepository repos) {
        _repos = repos;
    }

    /**
     * Return the schema group to reverse map. If none has been set, the
     * schema will be generated from the database.
     */
    public SchemaGroup getSchemaGroup() {
        if (_schema == null) {
            SchemaGenerator gen = new SchemaGenerator(_conf);
            try {
                gen.generateSchemas();
            } catch (SQLException se) {
                throw SQLExceptions.getStore(se,
                    _conf.getDBDictionaryInstance());
            }
            _schema = gen.getSchemaGroup();
        }
        return _schema;
    }

    /**
     * Set the schema to reverse map.
     */
    public void setSchemaGroup(SchemaGroup schema) {
        _schema = schema;
    }

    /**
     * Return the generated mappings.
     */
    public ClassMapping[] getMappings() {
        return getRepository().getMappings();
    }

    /**
     * Generate mappings and class code for the current schema group.
     */
    public void run() {
        // map base classes first
        Schema[] schemas = getSchemaGroup().getSchemas();
        Table[] tables;
        for (Schema schema2 : schemas) {
            tables = schema2.getTables();
            for (Table table : tables)
                if (isBaseTable(table))
                    mapBaseClass(table);
        }

        // map vertical subclasses
        Set subs = null;
        for (Schema schema1 : schemas) {
            tables = schema1.getTables();
            for (Table table : tables) {
                if (!_tables.containsKey(table)
                        && getSecondaryType(table, false) == TABLE_SUBCLASS) {
                    if (subs == null)
                        subs = new HashSet();
                    subs.add(table);
                }
            }
        }
        if (subs != null)
            mapSubclasses(subs);

        // map fields in the primary tables of the classes
        ClassMapping cls;
        for (Object o1 : _tables.values()) {
            cls = (ClassMapping) o1;
            mapColumns(cls, cls.getTable(), null, false);
        }

        // map association tables, join tables, and secondary tables
        for (Schema element : schemas) {
            tables = element.getTables();
            for (Table table : tables)
                if (!_tables.containsKey(table))
                    mapTable(table, getSecondaryType(table, false));
        }

        // map discriminators and versions, make sure identity type is correct,
        // set simple field column java types, and ref schema components so
        // we can tell what is unmapped
        FieldMapping[] fields;
        for (Object item : _tables.values()) {
            cls = (ClassMapping) item;
            cls.refSchemaComponents();
            if (cls.getDiscriminator().getStrategy() == null)
                getStrategyInstaller().installStrategy
                        (cls.getDiscriminator());
            cls.getDiscriminator().refSchemaComponents();
            if (cls.getVersion().getStrategy() == null)
                getStrategyInstaller().installStrategy(cls.getVersion());
            cls.getVersion().refSchemaComponents();

            // double-check identity type; if it was set for builtin identity
            // it might have to switch to std application identity if pk field
            // not compatible
            if (cls.getPCSuperclass() == null
                    && cls.getIdentityType() == ClassMetaData.ID_APPLICATION) {
                if (cls.getPrimaryKeyFields().length == 0)
                    throw new MetaDataException(_loc.get("no-pk-fields", cls));
                if (cls.getObjectIdType() == null
                        || (cls.isOpenJPAIdentity() && !isBuiltinIdentity(cls)))
                    setObjectIdType(cls);
            }
            else if (cls.getIdentityType() == ClassMetaData.ID_DATASTORE)
                cls.getPrimaryKeyColumns()[0].setJavaType(JavaTypes.LONG);

            // set java types for simple fields;
            fields = cls.getDeclaredFieldMappings();
            for (FieldMapping field : fields) {
                field.refSchemaComponents();
                setColumnJavaType(field);
                setColumnJavaType(field.getElementMapping());
            }
        }

        // set the java types of foreign key columns; we couldn't do this
        // earlier because we rely on the linked-to columns to do it
        for (Object value : _tables.values()) {
            cls = (ClassMapping) value;
            setForeignKeyJavaType(cls.getJoinForeignKey());

            fields = cls.getDeclaredFieldMappings();
            for (FieldMapping field : fields) {
                setForeignKeyJavaType(field.getJoinForeignKey());
                setForeignKeyJavaType(field.getForeignKey());
                setForeignKeyJavaType(field.getElementMapping().
                        getForeignKey());
            }
        }

        // allow customizer to map unmapped tables, and warn about anything
        // that ends up unmapped
        Column[] cols;
        Collection unmappedCols = new ArrayList(5);
        for (Schema schema : schemas) {
            tables = schema.getTables();
            for (Table table : tables) {
                unmappedCols.clear();
                cols = table.getColumns();
                for (Column col : cols)
                    if (col.getRefCount() == 0)
                        unmappedCols.add(col);

                if (unmappedCols.size() == cols.length) {
                    if (_custom == null || !_custom.unmappedTable(table))
                        _log.info(_loc.get("unmap-table", table));
                }
                else if (unmappedCols.size() > 0)
                    _log.info(_loc.get("unmap-cols", table, unmappedCols));
            }
        }
        if (_custom != null)
            _custom.close();

        // resolve mappings
        for (Object o : _tables.values()) {
            ((ClassMapping) o).resolve(MODE_META | MODE_MAPPING);
        }
    }

    /**
     * Map the table of the given type.
     */
    private void mapTable(Table table, int type) {
        switch (type) {
            case TABLE_SECONDARY:
            case TABLE_SECONDARY_OUTER:
                mapSecondaryTable(table, type != TABLE_SECONDARY);
                break;
            case TABLE_ASSOCIATION:
                mapAssociationTable(table);
                break;
        }
    }

    /**
     * Return true if the given class is compatible with builtin identity.
     */
    private static boolean isBuiltinIdentity(ClassMapping cls) {
        FieldMapping[] fields = cls.getPrimaryKeyFieldMappings();
        if (fields.length != 1)
            return false;
        switch (fields[0].getDeclaredTypeCode()) {
            case JavaTypes.BYTE:
            case JavaTypes.CHAR:
            case JavaTypes.INT:
            case JavaTypes.LONG:
            case JavaTypes.SHORT:
            case JavaTypes.BYTE_OBJ:
            case JavaTypes.CHAR_OBJ:
            case JavaTypes.INT_OBJ:
            case JavaTypes.LONG_OBJ:
            case JavaTypes.SHORT_OBJ:
            case JavaTypes.STRING:
            case JavaTypes.OID:
                return true;
        }
        return false;
    }

    /**
     * Set the Java type of the column for the given value.
     */
    private static void setColumnJavaType(ValueMapping vm) {
        Column[] cols = vm.getColumns();
        if (cols.length == 1)
            cols[0].setJavaType(vm.getDeclaredTypeCode());
    }

    /**
     * Set the Java type of the foreign key columns.
     */
    private static void setForeignKeyJavaType(ForeignKey fk) {
        if (fk == null)
            return;
        Column[] cols = fk.getColumns();
        Column[] pks = fk.getPrimaryKeyColumns();
        for (int i = 0; i < cols.length; i++)
            if (cols[i].getJavaType() == JavaTypes.OBJECT)
                cols[i].setJavaType(pks[i].getJavaType());
    }

    /**
     * Uses {@link CodeGenerator}s to write the Java code for the generated
     * mappings to the proper packages.
     *
     * @return a list of {@link File} instances that were written
     */
    public List recordCode() throws IOException {
        return recordCode(null);
    }

    /**
     * Write the code for the tool.
     *
     * @param output if null, then perform the write directly
     * to the filesystem; otherwise, populate the
     * specified map with keys as the generated
     * {@link ClassMapping} and values as a
     * {@link String} that contains the generated code
     * @return a list of {@link File} instances that were written
     */
    public List recordCode(Map<Class<?>, String> output)
        throws IOException {
        List written = new LinkedList();

        ClassMapping[] mappings = getMappings();
        ReverseCodeGenerator gen;
        for (ClassMapping mapping : mappings) {
            if (_log.isInfoEnabled())
                _log.info(_loc.get("class-code", mapping));

            ApplicationIdTool aid = newApplicationIdTool(mapping);
            if (getGenerateAnnotations())
                gen = new AnnotatedCodeGenerator(mapping, aid);
            else
                gen = new ReverseCodeGenerator(mapping, aid);

            gen.generateCode();

            if (output == null) {
                gen.writeCode();
                written.add(gen.getFile());
                if (aid != null && !aid.isInnerClass())
                    aid.record();
            }
            else {
                StringWriter writer = new StringWriter();
                gen.writeCode(writer);
                output.put(mapping.getDescribedType(), writer.toString());

                if (aid != null && !aid.isInnerClass()) {
                    writer = new StringWriter();
                    aid.setWriter(writer);
                    aid.record();
                    output.put(mapping.getObjectIdType(),
                            writer.toString());
                }
            }
        }
        return written;
    }

    /**
     * Write the generated metadata to the proper packages.
     *
     * @return the set of metadata {@link File}s that were written
     */
    public Collection recordMetaData(boolean perClass)
        throws IOException {
        return recordMetaData(perClass, null);
    }

    /**
     * Write the code for the tool.
     *
     * @param output if null, then perform the write directly
     * to the filesystem; otherwise, populate the
     * specified map with keys as the generated
     * {@link ClassMapping} and values as a
     * {@link String} that contains the generated code
     * @return the set of metadata {@link File}s that were written
     */
    public Collection recordMetaData(boolean perClass, Map output)
        throws IOException {
        // pretend mappings are all resolved
        ClassMapping[] mappings = getMappings();
        for (ClassMapping classMapping : mappings) {
            classMapping.setResolve(MODE_META | MODE_MAPPING, true);
            classMapping.setUseSchemaElement(getUseSchemaElement());
        }
        // store in user's configured IO
        MetaDataFactory mdf = _conf.newMetaDataFactoryInstance();
        mdf.setRepository(getRepository());
        mdf.setStoreDirectory(_dir);
        if (perClass)
            mdf.setStoreMode(MetaDataFactory.STORE_PER_CLASS);
        mdf.store(mappings, new QueryMetaData[0], new SequenceMetaData[0],
            MODE_META | MODE_MAPPING, output);

        Set files = new TreeSet();
        for (ClassMapping mapping : mappings)
            if (mapping.getSourceFile() != null)
                files.add(mapping.getSourceFile());
        return files;
    }

    public void buildAnnotations() {
        Map output = new HashMap();
        // pretend mappings are all resolved
        ClassMapping[] mappings = getMappings();
        for (ClassMapping mapping : mappings) {
            mapping.setResolve(MODE_META | MODE_MAPPING, true);
            mapping.setUseSchemaElement(getUseSchemaElement());
        }
        // store in user's configured IO
        MetaDataFactory mdf = _conf.newMetaDataFactoryInstance();
        mdf.setRepository(getRepository());
        mdf.setStoreDirectory(_dir);
        mdf.store(mappings, new QueryMetaData[0], new SequenceMetaData[0],
            MODE_META | MODE_MAPPING | MODE_ANN_MAPPING, output);
        _annos = output;
    }

    /**
     * Returns a list of stringified annotations for specified meta.
     */
    public List getAnnotationsForMeta(Object meta) {
        if (null == _annos)
            return null;
        return (List) _annos.get(meta);
    }

    /**
     * Generate and write the application identity code.
     */
