/*
 * Copyright  2001-2002,2004 The Apache Software Foundation
 *
 *  Licensed 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.optional.metamata;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.taskdefs.LogStreamHandler;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;

/**
 * Invokes the Metamata Audit/ Webgain Quality Analyzer on a set of Java files.
 * <p>
 * <i>maudit</i> performs static analysis of the Java source code and byte
 * code files to find and report errors of style and potential problems related
 * to performance, maintenance and robustness. As a convenience, a stylesheet
 * is given in <tt>etc</tt> directory, so that an HTML report can be generated
 * from the XML file.
 *
 */
public class MAudit extends AbstractMetamataTask {

    /* As of Metamata 2.0, the command line of MAudit is as follows:
    Usage
        maudit <option>... <path>... [-unused <search-path>...]

    Parameters
        path               File or directory to audit.
        search-path        File or directory to search for declaration uses.

    Options
        -arguments  -A     <file>     Includes command line arguments from file.
        -classpath  -cp    <path>     Sets class path (also source path unless one
                                      explicitly set). Overrides METAPATH/CLASSPATH.
        -exit       -x                Exits after the first error.
        -fix        -f                Automatically fixes certain errors.
        -fullpath                     Prints full path for locations.
        -help       -h                Prints help and exits.
        -list       -l                Creates listing file for each audited file.
        -offsets    -off              Offset and length for locations.
        -output     -o     <file>     Prints output to file.
        -quiet      -q                Suppresses copyright and summary messages.
        -sourcepath        <path>     Sets source path. Overrides SOURCEPATH.
        -tab        -t                Prints a tab character after first argument.
        -unused     -u                Finds declarations unused in search paths.
        -verbose    -v                Prints all messages.
        -version    -V                Prints version and exits.
    */

    //---------------------- PUBLIC METHODS ------------------------------------

    /** pattern used by maudit to report the error for a file */
    /** RE does not seems to support regexp pattern with comments so i'm stripping it*/
    // (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*)
    static final String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)";

    private File outFile = null;

    private Path searchPath = null;

    private Path rulesPath = null;

    private boolean fix = false;

    private boolean list = false;

    private boolean unused = false;

//  add a bunch of undocumented options for the task
    private boolean quiet = false;
    private boolean exit = false;
    private boolean offsets = false;
    private boolean verbose = false;
    private boolean fullsemanticize = false;

    /** default constructor */
    public MAudit() {
        super("com.metamata.gui.rc.MAudit");
    }

    /**
     * The XML file to which the Audit result should be written to; required
     */

    public void setTofile(File outFile) {
        this.outFile = outFile;
    }

    /**
     * Automatically fix certain errors
     * (those marked as fixable in the manual);
     * optional, default=false
     */
    public void setFix(boolean flag) {
        this.fix = flag;
    }

    /**
     * Creates listing file for each audited file; optional, default false.
     * When set, a .maudit file will be generated in the
     * same location as the source file.
     */
    public void setList(boolean flag) {
        this.list = flag;
    }

    /**
     * Finds declarations unused in search paths; optional, default false.
     * It will look for unused global declarations
     * in the source code within a use domain specified by the
     * <tt>searchpath</tt> element.
     */
    public void setUnused(boolean flag) {
        this.unused = flag;
    }

    /**
     * flag to suppress copyright and summary messages; default false.
     * internal/testing only
     * @ant.attribute ignore="true"
     */
    public void setQuiet(boolean flag) {
        this.quiet = flag;
    }

    /**
     * flag to tell the task to exit after the first error.
     * internal/testing only
     * @ant.attribute ignore="true"
     */
    public void setExit(boolean flag) {
        this.exit = flag;
    }

    /**
     * internal/testing only
     * @ant.attribute ignore="true"
     */
    public void setOffsets(boolean flag) {
        this.offsets = flag;
    }

    /**
     * flag to print all messages; optional, default false.
     * internal/testing only
     * @ant.attribute ignore="true"
     */
    public void setVerbose(boolean flag) {
        this.verbose = flag;
    }

    /**
     * internal/testing only
     * @ant.attribute ignore="true"
     */
    public void setFullsemanticize(boolean flag) {
        this.fullsemanticize = flag;
    }

    /**
     * classpath for additional audit rules
     * these must be placed before metamata.jar !!
     */
    public Path createRulespath() {
        if (rulesPath == null) {
            rulesPath = new Path(getProject());
        }
        return rulesPath;
    }

    /**
     * search path to use for unused global declarations;
     * required when <tt>unused</tt> is set.
     */
    public Path createSearchpath() {
        if (searchPath == null) {
            searchPath = new Path(getProject());
        }
        return searchPath;
    }

    /**
     * create the option vector for the command
     */
    protected Vector getOptions() {
        Vector options = new Vector(512);
        // add the source path automatically from the fileset.
        // to avoid redundancy...
        for (int i = 0; i < fileSets.size(); i++) {
            FileSet fs = (FileSet) fileSets.elementAt(i);
            Path path = createSourcepath();
            File dir = fs.getDir(getProject());
            path.setLocation(dir);
        }

        // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
        // not work. So we will use the sourcepath prepended to classpath. (order
        // is important since Metamata looks at .class and .java)
        if (sourcePath != null) {
            sourcePath.append(classPath); // srcpath is prepended
            classPath = sourcePath;
            sourcePath = null; // prevent from using -sourcepath
        }

        // don't forget to modify the pattern if you change the options reporting
        if (classPath != null) {
            options.addElement("-classpath");
            options.addElement(classPath.toString());
        }
        // suppress copyright msg when running, we will let it so that this
        // will be the only output to the console if in xml mode
        if (quiet) {
            options.addElement("-quiet");
        }
        if (fullsemanticize) {
            options.addElement("-full-semanticize");
        }
        if (verbose) {
            options.addElement("-verbose");
        }
        if (offsets) {
            options.addElement("-offsets");
        }
        if (exit) {
            options.addElement("-exit");
        }
        if (fix) {
            options.addElement("-fix");
        }
        options.addElement("-fullpath");

        // generate .maudit files much more detailed than the report
        // I don't like it very much, I think it could be interesting
        // to get all .maudit files and include them in the XML.
        if (list) {
            options.addElement("-list");
        }
        if (sourcePath != null) {
            options.addElement("-sourcepath");
            options.addElement(sourcePath.toString());
        }
        addAllVector(options, includedFiles.keys());
        if (unused) {
            options.addElement("-unused");
            options.addElement(searchPath.toString());
        }
        return options;
    }

    /**
     * validate the settings
     */
    protected void checkOptions() throws BuildException {
        super.checkOptions();
        if (unused && searchPath == null) {
            throw new BuildException("'searchpath' element must be set when "
                + "looking for 'unused' declarations.");
        }
        if (!unused && searchPath != null) {
            log("'searchpath' element ignored. 'unused' attribute is disabled.",
                Project.MSG_WARN);
        }
        if (rulesPath != null) {
            cmdl.createClasspath(getProject()).addExisting(rulesPath);
        }
    }

    protected ExecuteStreamHandler createStreamHandler() throws BuildException {
        // if we didn't specify a file, then use a screen report
        if (outFile == null) {
            return new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_ERR);
        }
        ExecuteStreamHandler handler = null;
        OutputStream out = null;
        try {
            out = new FileOutputStream(outFile);
            handler = new MAuditStreamHandler(this, out);
        } catch (IOException e) {
            throw new BuildException(e);
        } finally {
            if (out == null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
        return handler;
    }

    protected void cleanUp() throws BuildException {
        super.cleanUp();
        // at this point if -list is used, we should move
        // the .maudit file since we cannot choose their location :(
        // the .maudit files match the .java files
        // we'll use includedFiles to get the .maudit files.

        /*if (out != null) {
            // close it if not closed by the handler...
        }*/
    }

}

