package org.apache.maven.javadoc;

/* ====================================================================
 *   Copyright 2001-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.
 * ====================================================================
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.collections.SetUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Converts the javadoc warnings into an xml (xdoc format) file.
 *
 * @author Steven Caswell (stevencaswell at apache.org)
 * @version $Id$
 */
public class JavadocWarningsTextToXml
{
    /** Number of args on the command line.*/
    private static final int ARGS_LENGTH = 3;

    /** XML padding. */
    private static final int PADDING = 10;

    /** Full path for the input file (javadoc plain text report). */
    private String inputFileName;

    /** XML encoding. */
    private String outputEncoding;

    /** Full path for the generated XDoc file. */
    private String outputFileName;

    /** Verbose flag. */
    private boolean verbose = false;

    /**
     * Invokes an instance of <code>JavadocWarningsTextToXml</code> from the
     * command line.  The following command line arguments are expected in this
     * order:
     *
     * <ol>
     * <li>
     * input file name
     * </li>
     * <li>
     * output file name
     * </li>
     * <li>
     * output encoding
     * </li>
     * </ol>
     *
     *
     * @param args the command line arguments
     *
     * @throws IOException if an exception occurs opening, reading, or writing
     *         files
     */
    public static void main( final String[] args )
        throws IOException
    {
        if ( args.length != ARGS_LENGTH )
        {
            throw new IllegalArgumentException( "Wrong number of arguments" );
        }

        JavadocWarningsTextToXml runner = new JavadocWarningsTextToXml();
        int i = 0;

        runner.setInputFileName( args[i++] );
        runner.setOutputFileName( args[i++] );
        runner.setOutputEncoding( args[i++] );
        runner.build();
    }

    /**
     * Sets the name of the input file. This file is expected to be a report
     * generated by Javadoc.
     *
     * @param inputFileNameValue the input file name
     */
    public void setInputFileName( final String inputFileNameValue )
    {
        if ( this.isVerbose() )
        {
            System.out.println( "Setting input file name: '" + inputFileNameValue + "'" );
        }

        this.inputFileName = inputFileNameValue;
    }

    /**
     * Returns the name of the input file.
     *
     * @return the inptu file name
     */
    public String getInputFileName()
    {
        return this.inputFileName;
    }

    /**
     * Sets the output encoding.
     *
     * @param outputEncodingValue the output encoding
     */
    public void setOutputEncoding( final String outputEncodingValue )
    {
        this.outputEncoding = outputEncodingValue;
    }

    /**
     * Returns the output encoding.
     *
     * @return the output encoding
     */
    public String getOutputEncoding()
    {
        return this.outputEncoding;
    }

    /**
     * Sets the output file name.
     *
     * @param outputFileNameValue the output file name
     */
    public void setOutputFileName( final String outputFileNameValue )
    {
        if ( this.isVerbose() )
        {
            System.out.println( "Setting output file name: '" + outputFileNameValue + "'" );
        }

        this.outputFileName = outputFileNameValue;
    }

    /**
     * Returns the name of the output file.
     *
     * @return the output file name
     */
    public String getOutputFileName()
    {
        return this.outputFileName;
    }

    /**
     * Sets the verbose mode. <br>
     *
     * @param verboseValue the verbose mode.
     */
    public void setVerbose( final boolean verboseValue )
    {
        this.verbose = verboseValue;

        if ( this.isVerbose() )
        {
            System.out.println( "verbose is true" );
        }
    }

    /**
     * Builds the xml file from the javadoc report input.
     *
     * @throws FileNotFoundException if the input file cannot be opened for
     *         reading or the output file cannot be opened for writing
     * @throws IOException if an error occurs reading or writing
     * @throws UnsupportedEncodingException if an unknown encoding was specified
     */
    public void build()
        throws FileNotFoundException, IOException, UnsupportedEncodingException
    {
        if ( StringUtils.isBlank( getInputFileName() ) )
        {
            throw new IllegalArgumentException( "Input file name must be specified" );
        }

        if ( StringUtils.isBlank( getOutputFileName() ) )
        {
            throw new IllegalArgumentException( "Output file name must be specified" );
        }

        String[] lines = this.readInput();
        Map fileMap = this.buildMap( lines );

        this.buildOutput( fileMap );
    }

    /**
     * Returns the verbose.
     *
     * @return Returns the verbose.
     */
    public boolean isVerbose()
    {
        return verbose;
    }

    /**
     * Build a Map from the javadoc report lines.
     * @param input the lines
     * @return the Map of files with javadoc errors.
     */
    private Map buildMap( final String[] input )
    {
        if ( this.isVerbose() )
        {
            System.out.println( "Building map from " + input.length + " input line(s)" );
        }

        Map files = new TreeMap();

        for ( int i = 0; i < input.length; i++ )
        {
            String line = input[i];

            if ( this.isVerbose() )
            {
                System.out.println( "Parsing line " + line );
            }

            try
            {
                // Break up line into pieces
                int javaDocStart = line.indexOf( "[javadoc]" );
                int fileNameStart = javaDocStart += PADDING;
                int warningStart = line.indexOf( "warning - " );
                int fileNameEnd = warningStart - 1;
                String fileNameAndLineNumber = line.substring( fileNameStart, fileNameEnd );
                int lastColon = fileNameAndLineNumber.lastIndexOf( ':' );
                int nextToLastColon = fileNameAndLineNumber.lastIndexOf( ':', lastColon - 1 );
                String fileName = fileNameAndLineNumber.substring( 0, nextToLastColon );
                String lineNumber = fileNameAndLineNumber.substring( nextToLastColon + 1, lastColon );
                int msgStart = warningStart + PADDING;
                String msg = line.substring( msgStart );

                // Get the messages for the file
                Map fileMessages = (Map) files.get( fileName );

                if ( fileMessages == null )
                {
                    fileMessages = new TreeMap();
                    files.put( fileName, fileMessages );
                }

                // Get the messages for the line
                Set lineMessages = (Set) fileMessages.get( new Integer( lineNumber ) );

                if ( lineMessages == null )
                {
                    lineMessages = SetUtils.orderedSet( new TreeSet() );

                    fileMessages.put( new Integer( lineNumber ), lineMessages );
                }

                // Put the message into the line messages set
                lineMessages.add( msg );
            }
            catch ( Throwable t )
            {
                System.err.println( "*** WARNING: exception parsing line '" + line + "': " + t.getMessage() );

                if ( this.isVerbose() )
                {
                    t.printStackTrace();
                }
            }
        } // for

        return files;
    }

    /**
     * Build the output on the disk.
     * @param fileMap The map containing the results.
     * @throws FileNotFoundException If the file cannot be created.
     * @throws UnsupportedEncodingException If the encoding isn't supported.
     */
    private void buildOutput( final Map fileMap )
        throws FileNotFoundException, UnsupportedEncodingException
    {
        File output = new File( this.outputFileName );
        File dir = output.getParentFile();

        if ( dir != null )
        {
            dir.mkdirs();
        }

        PrintWriter out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( output ), this
            .getOutputEncoding() ) );

        writeOutput( fileMap, out );
        out.flush();
        out.close();
    }

    /**
     * Extract warning lines from the javadoc result.
     * @return a table of lines.
     * @throws FileNotFoundException If the javadoc result can't be read.
     * @throws IOException If there's a problem while reading the javadoc result.
     */
    private String[] readInput()
        throws FileNotFoundException, IOException
    {
        if ( this.isVerbose() )
        {
            System.out.println( "Reading '" + getInputFileName() + "'" );
        }

        BufferedReader reader = new BufferedReader( new FileReader( getInputFileName() ) );

        List lines = new ArrayList();
        String line = null;

        while ( ( line = reader.readLine() ) != null )
        {
            // Look for lines containing the string "warning - "
            if ( line.indexOf( "warning - " ) >= 0 )
            {
                lines.add( line );
            }
        }

        reader.close();

        if ( this.isVerbose() )
        {
            System.out.println( "Read " + lines.size() + " line(s) from input file" );
        }

        return (String[]) lines.toArray( new String[lines.size()] );
    }

    /**
     * Write the file map on the output
     * @param fileMap The filemap to translate in XML.
     * @param out The output to write.
     */
    private void writeOutput( final Map fileMap, final PrintWriter out )
    {
        if ( this.isVerbose() )
        {
            System.out.println( "Writing to output file '" + this.outputFileName );
        }

        out.println( "<?xml version=\"1.0\" encoding=\"" + this.getOutputEncoding() + "\"?>" );
        out.println( "<javadoc>" );

        for ( Iterator fileMapIterator = fileMap.keySet().iterator(); fileMapIterator.hasNext(); )
        {
            String fileName = (String) fileMapIterator.next();

            out.println( "<file name=\"" + fileName + "\">" );

            Map fileMessages = (Map) fileMap.get( fileName );

            for ( Iterator fileMessagesIterator = fileMessages.entrySet().iterator(); fileMessagesIterator.hasNext(); )
            {
                Map.Entry entry = (Map.Entry) fileMessagesIterator.next();
                Integer lineNumber = (Integer) entry.getKey();
                Set lineMessages = (Set) entry.getValue();

                for ( Iterator lineMessagesIterator = lineMessages.iterator(); lineMessagesIterator.hasNext(); )
                {
                    String msg = (String) lineMessagesIterator.next();

                    out.println( "<error line=\"" + lineNumber + "\" severity=\"warning\" message=\""
                        + StringEscapeUtils.escapeXml( msg ) + "\"/>" );
                }
            }

            out.println( "</file>" );
        }

        out.println( "</javadoc>" );

        if ( this.isVerbose() )
        {
            System.out.println( "Finished writing output file" );
        }
    }
}
