/* ConvertComments.java, was
** jd_conv_comm - convert Java doc comments from /// style to /** style
**
** Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
** Converted from Flex to Java George Ruban gruban@geocities.com 10/3/97
** http://www.oocities.com/SiliconValley/Vista/2013
*/

package Acme; // in honor of Jef P.

import java.io.*;

/** Convert Java doc comments from /// style to /** style.
 * <P>
 * Jef Poskanzer's marvelous <A HREF="http://www.acme.com/java/software/"
 * >Acme Java library</A> is indispensible to many a Java coding effort,
 * but is commented with a non-standard Javadoc style.
 * He provided a Flex file to convert those comments to more standard 
 * Javadoc style, but I couldn't get it to compile on many of the
 * systems I work on - so instead, I rewrote it in Java.
 * <P>
 * Having it as a Java class ensures it will be useable at all times
 * when Java is. Also, as an instantiable class, it should
 * be useable in your programs internally (if you are writing
 * a code editor, or compiler, or pretty-printer, or something -
 * there seem to be quite a few of those sorts of things being
 * written for Java).
 * <P>
 * It should also fix a few bugs in the existing Flex file: 
 * <LI> An escaped quote (\" or \') would be considered to
 * end a quoted state, causing all sorts of subsequent trouble
 * <LI> The comment indentation would be lost
 * <PRE>
 *          /// My method
 *          //  which does nothing
 *          public void foo()
 * </PRE>
 * would become
 * <PRE>
 *          /** My method
 *          *  which does nothing
 *     * /                   
 *          public void foo()
 * </PRE>
 * fixed to being
 * <PRE>
 *          /** My method 
 *           *  which does nothing * /                   
 *          public void foo()
 * </PRE>
 * <P>
 * Enjoy. If you like it, you can visit my 
 * <A HREF="http://www.oocities.com/SiliconValley/Vista/2013/">GeoCities 
 * homepage</A>, and tell me as much by signing my guestbook.
 * <P>
 * <A HREF="ConvertComments.java">The source code</A>.
 * <P>
 * George Ruban, 10-3-97.
 *
 * @author George Ruban (from Jef Poskanzer's Flex code)
 * @version 1.0 10-3-97 */
public class ConvertComments
{
    /** Set to true for run-time trace to System.err. */
    boolean debug;

    /** Stream reading from. */
    private InputStream in;
    
    /** Stream writing to. */
    private OutputStream out;
    
    /** Buffer of last 3 characters written. Used to determine state. */
    private byte buf[] = new byte[3];

    /** At what point is the parser in the code?
     * CODE, DOCSLASHCOMM, STARCOMM, SLASHCOMM, DQUOTE, or SQUOTE. */
    private int state;
    
    /** Inside normal Java code. */
    static final int CODE = 0;

    /** Inside a /// Javadoc comment. */
    static final int DOCSLASHCOMM = 1;

    /** Before the first // on a new line of a /// Javadoc comment. */
    static final int DOCSLASHCOMM_NEWLINE = 2;

    /** Inside a /* Java multi line comment. */
    static final int STARCOMM = 3;
    
    /** Inside a // Java single line comment. */
    static final int SLASHCOMM = 4;
    
    /** Inside a " double quoted segment. */
    static final int DQUOTE = 5;
    
    /** Inside a ' single quoted segment. */
    static final int SQUOTE = 6;
    
    /** Converts state to string form. */
    static String toString(int state)
    {
        String[] states = {
            "CODE", "DOCSLASHCOMM", "DOCSLASHCOMM_NEWLINE",
            "STARCOMM", "SLASHCOMM", "DQUOTE", "SQUOTE"
        };
        return states[state];
    }

    /** Writes first byte, rotates buffer. */
    private void echoByte() throws IOException
    {
        out.write(buf[0]);
        readByte();
    }
    
    
    /** Read in a new byte from input at the end of the buffer,
     * flushing beginning of buffer. */
    private void readByte() throws IOException
    {
        buf[0] = buf[1];
        buf[1] = buf[2];
        int i = in.read();
        if(i == -1) // end of input file
            throw new EOFException();
        else
            buf[2] = (byte)i;
    }
    

    /** Convert Java source code from input stream to output stream. */
    public void convert(InputStream i, OutputStream o)
        throws IOException
    {
        state = CODE;
        in = i;
        if(!(in instanceof BufferedInputStream)) 
            in = new BufferedInputStream(in);
        out = o;
        StringBuffer sbuf = new StringBuffer(); // holds DOCSLASHCOMM_NEWLINEs
        int lastState = -1;

        try 
        {
            // initialize buffer w/first 3 characters
            in.read(buf);
            
            while(true)
            {
                if(debug)
                {
                    if(lastState != state)
                    {
                        lastState = state;
                        System.err.println();
                        System.err.println("state: " + toString(state));
                    }
                    System.err.print((char)buf[0]);
                }
                
                switch(state)
                {
                case CODE:
                    if(buf[0] == '/' && buf[1] == '/' && buf[2] == '/')
                    {
                        state = DOCSLASHCOMM;
                        buf[1] = buf[2] = (byte)'*';
                    }
                    else
                    {
                        if(buf[0] == '/' && buf[1] == '*')
                            state = STARCOMM;
                        else if(buf[0] == '/' && buf[1] == '/')
                            state = SLASHCOMM;
                        else if(buf[0] == '"')
                            state = DQUOTE;
                        else if(buf[0] == '\'')
                            state = SQUOTE;
                    }
                    echoByte();
                    break;
                    
                case DOCSLASHCOMM:
                    if(buf[0] == '\n')
                    {
                        state = DOCSLASHCOMM_NEWLINE;
                        sbuf.append((char)buf[0]);
                        readByte();
                    }
                    else
                        echoByte();
                    break;
                    
                case DOCSLASHCOMM_NEWLINE:
                    if(buf[0] == ' ' || buf[0] == '\t')
                    {
                        sbuf.append((char)buf[0]);
                        readByte();
                    }
                    else
                    {
                        if(buf[0] == '/' && buf[1] == '/')
                        {
                            for(int j=0; j<sbuf.length(); ++j)
                                out.write(sbuf.charAt(j));
                            state = DOCSLASHCOMM;
                            buf[0] = ' ';
                            buf[1] = '*';
                        }
                        else
                        {
                            state = CODE;
                            out.write(' ');
                            out.write('*');
                            out.write('/');
                            for(int j=0; j<sbuf.length(); ++j)
                                out.write(sbuf.charAt(j));
                            echoByte();
                        }
                        sbuf.setLength(0);
                    }
                    break;
                    
                case STARCOMM:
                    if(buf[0] == '*' && buf[1] == '/')
                        state = CODE;
                    echoByte();
                    break;
                    
                case SLASHCOMM:
                    if(buf[0] == '\n')
                        state = CODE;
                    echoByte();
                    break;
                    
                case DQUOTE:
                    if(buf[0] == '\\') // just echo escaped quoted characters
                        echoByte();
                    else if(buf[0] == '"')
                        state = CODE;
                    echoByte();
                    break;
                    
                case SQUOTE:
                    if(buf[0] == '\\') // just echo escaped quoted characters
                        echoByte();
                    else if(buf[0] == '\'')
                        state = CODE;
                    echoByte();
                    break;
                    
                default:
                    throw new InternalError("Unknown state " + state + 
                                            " reached!");
                }
            }
        }
        catch(EOFException endOfFile)
        {
            for(int j=0; j<sbuf.length(); ++j)
                out.write(sbuf.charAt(j));
            out.write(buf[0]);
            out.write(buf[1]);
            return;
        }
    }
    
    /** Read and write Java source code files. 
     * With no arguments, reads from System.in, writes to System.out.
     * If given file names overwrites files in place.
     * Pass a -d to start debugging output to System.err.
     * Usage: java Acme.ConvertComments [ -d | File.java ...]
     */
    public static void main(String [] args) throws IOException
    {
        ConvertComments c = new ConvertComments();

        if(args.length == 0 || args.length == 1 && args[0].equals("-d"))
        {
            c.debug = (args.length == 1);
            c.convert(System.in, System.out);
        }
        else
        {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            for(int i=0; i<args.length; ++i)
            {
                if(args[i].equals("-d"))
                { 
                    System.out.println("Debugging enabled.");
                    c.debug = true;
                    continue;
                }
                out.reset();
                System.out.println("Converting comments in " + args[i]);
                FileInputStream in = new FileInputStream(args[i]);
                c.convert(in, out);
                in.close();
                new FileOutputStream(args[i]).write(out.toByteArray());
            }
        }
    }
}
