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

import java.io.File;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Vector;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Apt;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.Path;


/**
 * <p>The implementation of the apt compiler for JDK 1.5.</p>
 *
 * <p>As usual, the low level entry points for Java tools are neither documented or
 * stable; this entry point may change from that of 1.5.0_01-b08 without any
 * warning at all. The IDE decompile of the tool entry points is as follows:</p>
 * <pre>
 * public class Main {
 * public Main() ;
 *
 * public static transient void main(String... strings);
 *
 * public static transient int process(String... strings);
 *
 * public static transient int process(PrintWriter printWriter,
 *      String... strings);
 * public static transient int process(
 *      AnnotationProcessorFactory annotationProcessorFactory,
 *      String... strings);
 *
 * public static transient int process(
 *      AnnotationProcessorFactory annotationProcessorFactory,
 *      PrintWriter printWriter,
 *      String... strings);
 * private static transient int processing(
 *      AnnotationProcessorFactory annotationProcessorFactory,
 *      PrintWriter printWriter,
 *      String... strings) ;
 * }
 * </pre>
 *
 * This Adapter is designed to run Apt in-JVM, an option that is not actually
 * exposed to end-users, because it was too brittle during beta testing; classpath
 * problems being the core issue.
 *
 * @since Ant 1.7
 */
public class AptCompilerAdapter extends DefaultCompilerAdapter {

    /**
     * Integer returned by the Apt compiler to indicate success.
     */
    private static final int APT_COMPILER_SUCCESS = 0;
    /**
     * class in tools.jar that implements APT
     */
    public static final String APT_ENTRY_POINT = "com.sun.tools.apt.Main";

    /**
     * method used to compile.
     */
    public static final String APT_METHOD_NAME = "process";

    /**
     * Get the facade task that fronts this adapter
     *
     * @return task instance
     * @see DefaultCompilerAdapter#getJavac()
     */
    protected Apt getApt() {
        return (Apt) getJavac();
    }

    /**
     * Using the front end arguments, set up the command line to run Apt
     *
     * @param apt task
     * @param cmd command that is set up with the various switches from the task
     *            options
     */
    static void setAptCommandlineSwitches(final Apt apt, final Commandline cmd) {

        if (!apt.isCompile()) {
            cmd.createArgument().setValue("-nocompile");
        }

        // Process the factory class
        final String factory = apt.getFactory();
        if (factory != null) {
            cmd.createArgument().setValue("-factory");
            cmd.createArgument().setValue(factory);
        }

        // Process the factory path
        final Path factoryPath = apt.getFactoryPath();
        if (factoryPath != null) {
            cmd.createArgument().setValue("-factorypath");
            cmd.createArgument().setPath(factoryPath);
        }

        final File preprocessDir = apt.getPreprocessDir();
        if (preprocessDir != null) {
            cmd.createArgument().setValue("-s");
            cmd.createArgument().setFile(preprocessDir);
        }

        // Process the processor options
        final Vector options = apt.getOptions();
        final Enumeration elements = options.elements();
        Apt.Option opt;
        StringBuffer arg = null;
        while (elements.hasMoreElements()) {
            opt = (Apt.Option) elements.nextElement();
            arg = new StringBuffer();
            arg.append("-A").append(opt.getName());
            if (opt.getValue() != null) {
                arg.append("=").append(opt.getValue());
            }
            cmd.createArgument().setValue(arg.toString());
        }
    }

    /**
     * using our front end task, set up the command line switches
     *
     * @param cmd command line to set up
     */
    protected void setAptCommandlineSwitches(final Commandline cmd) {
        final Apt apt = getApt();
        setAptCommandlineSwitches(apt, cmd);
    }

    /**
     * Run the compilation.
     * @return true on success.
     * @throws BuildException if the compilation has problems.
     */
    public boolean execute() throws BuildException {
        attributes.log("Using apt compiler", Project.MSG_VERBOSE);
        //set up the javac options
        final Commandline cmd = setupModernJavacCommand();
        //then add the Apt options
        setAptCommandlineSwitches(cmd);

        //finally invoke APT
        // Use reflection to be able to build on all JDKs:
        try {
            final Class c = Class.forName(APT_ENTRY_POINT);
            final Object compiler = c.newInstance();
            final Method compile = c.getMethod(APT_METHOD_NAME,
                    new Class[]{(new String[]{}).getClass()});
            final int result = ((Integer) compile.invoke
                    (compiler, new Object[]{cmd.getArguments()}))
                    .intValue();
            return (result == APT_COMPILER_SUCCESS);
        } catch (final BuildException be) {
            //rethrow build exceptions
            throw be;
        } catch (final Exception ex) {
            //cast everything else to a build exception
            throw new BuildException("Error starting apt compiler",
                    ex, location);
        }
    }
}
