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

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.Properties;

import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.meta.PropertiesReverseCustomizer;
import org.apache.openjpa.jdbc.meta.ReverseCustomizer;
import org.apache.openjpa.jdbc.meta.ReverseMappingTool;
import org.apache.openjpa.lib.ant.AbstractTask;
import org.apache.openjpa.lib.conf.ConfigurationImpl;
import org.apache.openjpa.lib.conf.Configurations;
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.StringUtil;
import org.apache.tools.ant.types.EnumeratedAttribute;

/**
 * Executes the {@link ReverseMappingTool} on the specified XML files.
 * This task can take the following arguments:
 * <ul>
 * <li><code>package</code></li>
 * <li><code>directory</code></li>
 * <li><code>useSchemaName</code></li>
 * <li><code>useSchemaElement</code></li>
 * <li><code>useForeignKeyName</code></li>
 * <li><code>nullableAsObject</code></li>
 * <li><code>blobAsObject</code></li>
 * <li><code>typeMap</code></li>
 * <li><code>primaryKeyOnJoin</code></li>
 * <li><code>useDatastoreIdentity</code></li>
 * <li><code>useBuiltinIdentityClass</code></li>
 * <li><code>detachable</code></li>
 * <li><code>inverseRelations</code></li>
 * <li><code>discriminatorStrategy</code></li>
 * <li><code>versionStrategy</code></li>
 * <li><code>innerIdentityClasses</code></li>
 * <li><code>identityClassSuffix</code></li>
 * <li><code>metadata</code></li>
 * <li><code>customizerClass</code></li>
 * <li><code>customizerProperties</code></li>
 * </ul> The task also accepts an embedded <code>codeFormat</code> element with
 * attributes for the bean properties of the {@link CodeFormat}.
 */
public class ReverseMappingToolTask
    extends AbstractTask {

    protected ReverseMappingTool.Flags flags = new ReverseMappingTool.Flags();
    protected String dirName = null;
    protected String typeMap = null;
    protected String customizerProperties = null;
    protected String customizerClass =
        PropertiesReverseCustomizer.class.getName();

    /**
     * Default constructor.
     */
    public ReverseMappingToolTask() {
        flags.metaDataLevel = "package";
        flags.format = new CodeFormat();
    }

    /**
     * Set the package name for the generated classes.
     */
    public void setPackage(String pkg) {
        flags.packageName = pkg;
    }

    /**
     * Set the output directory for the generated classes.
     */
    public void setDirectory(String dirName) {
        this.dirName = dirName;
    }

    /**
     * Set whether to use the schema name when naming the classes.
     */
    public void setUseSchemaName(boolean useSchemaName) {
        flags.useSchemaName = useSchemaName;
    }

    /**
     * Set whether to use the schema name in generated files
     */
    public void setUseSchemaElement(boolean useSchemaElement) {
    	flags.useSchemaElement = useSchemaElement;
    }

    /**
     * Set whether to use foreign key names to name relations.
     */
    public void setUseForeignKeyName(boolean useForeignKeyName) {
        flags.useForeignKeyName = useForeignKeyName;
    }

    /**
     * Set whether to represent nullable columns as primitive wrappers.
     */
    public void setNullableAsObject(boolean nullableAsObject) {
        flags.nullableAsObject = nullableAsObject;
    }

    /**
     * Set whether to represent blob columns as Java objects rather than
     * byte[] fields.
     */
    public void setBlobAsObject(boolean blobAsObject) {
        flags.blobAsObject = blobAsObject;
    }

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

    /**
     * Set the SQL type map overrides.
     */
    public void setTypeMap(String typeMap) {
        this.typeMap = typeMap;
    }

    /**
     * Set whether to allow primary keys on join tables.
     */
    public void setPrimaryKeyOnJoin(boolean primaryKeyOnJoin) {
        flags.primaryKeyOnJoin = primaryKeyOnJoin;
    }

    /**
     * Set whether to use datastore identity by default.
     */
    public void setUseDataStoreIdentity(boolean useDataStoreIdentity) {
        flags.useDataStoreIdentity = useDataStoreIdentity;
    }

    /**
     * Set whether to use single field identity where possible.
     */
    public void setUseBuiltinIdentityClass(boolean useBuiltinIdentityClass) {
        flags.useBuiltinIdentityClass = useBuiltinIdentityClass;
    }

    /**
     * Set whether to generate inverse 1-many/1-1 relations for all many-1/1-1
     * relations.
     */
    public void setInverseRelations(boolean inverseRelations) {
        flags.inverseRelations = inverseRelations;
    }

    /**
     * Set whether to make generated classes detachable.
     */
    public void setDetachable(boolean detachable) {
        flags.detachable = detachable;
    }

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

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

    /**
     * Whether or not to generate application identity classes as inner classes.
     */
    public void setInnerIdentityClasses(boolean innerAppId) {
        flags.innerIdentityClasses = innerAppId;
    }

    /**
     * The suffix to use to create the identity class name for a class, or
     * for inner classes, the name of the inner class.
     */
    public void setIdentityClassSuffix(String suffix) {
        flags.identityClassSuffix = suffix;
    }

    /**
     * Set the level of the generated metadata.
     */
    public void setMetadata(Level level) {
        flags.metaDataLevel = level.getValue();
    }

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

    /**
     * Whether to use field or property-based access on generated code.
     * Defaults to field-based access.
     */
    public void setAccessType(AccessType accessType) {
        flags.accessType = accessType.getValue();
    }

    /**
     * Set a customizer class to use.
     */
    public void setCustomizerClass(String customizerClass) {
        this.customizerClass = customizerClass;
    }

    /**
     * Set a properties file to pass to the customizer class.
     */
    public void setCustomizerProperties(String customizerProperties) {
        this.customizerProperties = customizerProperties;
    }

    public Object createCodeFormat() {
        return flags.format;
    }

    @Override
    protected ConfigurationImpl newConfiguration() {
        return new JDBCConfigurationImpl();
    }

    @Override
    protected void executeOn(String[] files)
        throws Exception {
        ClassLoader loader = getClassLoader();
        if (!StringUtil.isEmpty(dirName))
            flags.directory = Files.getFile(dirName, loader);
        if (!StringUtil.isEmpty(typeMap))
            flags.typeMap = Configurations.parseProperties(typeMap);

        // load customizer properties
        Properties customProps = new Properties();
        File propsFile = Files.getFile(customizerProperties, loader);
        if (propsFile != null && AccessController.doPrivileged(
                J2DoPrivHelper.existsAction(propsFile))) {
            FileInputStream fis = null;
            try {
                fis = AccessController.doPrivileged(
                    J2DoPrivHelper.newFileInputStreamAction(propsFile));
            } catch (PrivilegedActionException pae) {
                 throw (FileNotFoundException) pae.getException();
            }
            customProps.load(fis);
        }

        // create and configure customizer
        JDBCConfiguration conf = (JDBCConfiguration) getConfiguration();
        flags.customizer = (ReverseCustomizer) Configurations.
            newInstance(customizerClass, conf, (String)null,
                AccessController.doPrivileged(
                    J2DoPrivHelper.getClassLoaderAction(
                        ReverseCustomizer.class)));
        if (flags.customizer != null)
            flags.customizer.setConfiguration(customProps);

        ReverseMappingTool.run(conf, files, flags, loader);
    }

    public static class Level
        extends EnumeratedAttribute {

        @Override
        public String[] getValues() {
            return new String[]{
                "package",
                "class",
                "none"
            };
        }
    }

    public static class AccessType
        extends EnumeratedAttribute {

        @Override
        public String[] getValues() {
            return new String[]{
                "field",
                "property"
            };
        }
    }
}
