// JavaAnalyzer class interface: generate cross-reference
//
// CONSTRUCTION: with a BufferedReader object
// ******************PUBLIC OPERATIONS***********************
// void generateCrossReference( ) --> Name says it all ...
// ******************ERRORS**********************************
// Error checking on comments and quotes is performed


// main generates cross-reference.

import java.io.*;
import DataStructures.*;
import Exceptions.*;
import Supporting.*;
import Supporting.Comparable;


/**
 * The basic object that will be stored in a search tree
 * for the cross-reference generator.
 * It implements the Comparable interface by providing
 * compares and lessThan.
 */
class IdNode implements Comparable
{
    String word;    // An identifier
    Queue  lines;   // Lines where it occurs

        // Constructor
    IdNode( String theWord, int currentLine )
    {
        word = new String( theWord );
        lines = new QueueAr( );
        lines.enqueue( new Integer( currentLine ) );
    }

        // Ordering methods
    public boolean lessThan( Comparable rhs )
    {
        return word.compareTo( ( (IdNode) rhs ).word ) < 0;
    }

    public int compares( Comparable rhs )
    {
        return word.compareTo( ( (IdNode) rhs ).word );
    }
}

/**
 * Class to perform cross reference
 * generation for Java programs.
 */
class JavaAnalyzer
{
    /**
     * Constructor.
     * @param inStream the stream containing a program.
     */
    public JavaAnalyzer( PushbackReader inStream )
    {
        ch = '\0';
        currentLine = 1;
        in = inStream;
        currentIdNode = new IdNode( "", 1 );
    }

    private static final int SLASH_SLASH = 0;
    private static final int SLASH_STAR  = 1;

    private PushbackReader in;    // The input stream
    private char ch;              // Current character
    private int currentLine;      // Current line
    private IdNode currentIdNode; // For Xref generator


    /**
     * nextChar sets ch based on the next character in the input stream.
     * putBackChar puts the character back onto the stream.
     * It should only be used once after a nextChar.
     * Both routines adjust currentLine if necessary.
     */

    private boolean nextChar( )
    {
        try
        {
            int readVal = in.read( );
            if( readVal == -1 )
                return false;
            ch = (char) readVal;
            if( ch == '\n' )
                currentLine++;
            return true;
        }
        catch( IOException e )
          { return false; }
    }

    private void putBackChar( )
    {
        if( ch == '\n' )
            currentLine--;
        try 
          { in.unread( (int) ch ); }
        catch( IOException e ) { }
    }


    /**
     * Precondition: We are about to process a comment; have already seen
     *                 comment-start token
     * Postcondition: Stream will be set immediately after
     *                 comment-ending token
     */
    private void skipComment( int start )
    {
        if( start == SLASH_SLASH )
        {
            while( nextChar( ) && ( ch != '\n' ) )
                ;
            return;
        }

            // Look for a */ sequence
        boolean state = false;   // True if we have seen *

        while( nextChar( ) )
        {
            if( state && ch == '/' )
                return;
            state = ( ch == '*' );
        }
        System.out.println( "Unterminated comment!" );
    }

    /**
     * Precondition: We are about to process a quote; have already seen
     *                   beginning quote.
     * Postcondition: Stream will be set immediately after
     *                   matching quote
     */
    private void skipQuote( char quoteType )
    {
        while( nextChar( ) )
        {
            if( ch == quoteType )
                return;
            if( ch == '\n' )
            {
                System.out.println( "Missing closed quote at line " +
                                    currentLine );
                return;
            }
            else if( ch == '\\' )
                nextChar( );
        }
    }

    /**
     * After the opening slash is seenm deal with next character.
     * If it is a comment starter, process it; otherwise putback
     * the next character if it is not a newline.
     */
    private void processSlash( )
    {
        if( nextChar( ) )
        {
            if( ch == '*' )
            {
                // Javadoc comment
                if( nextChar( ) && ch != '*' )
                    putBackChar( );
                skipComment( SLASH_STAR );
            }
            else if( ch == '/' )
                skipComment( SLASH_SLASH );
            else if( ch != '\n' )
                putBackChar( );
        }
    }

    /**
     * Return true if ch can be part of a Java identifier
     */
    private boolean isIdChar( char ch )
    {
        return ch == '_' || Character.isUpperCase( ch ) ||
               Character.isLowerCase( ch ) || Character.isDigit( ch );
    }

    /**
     * Return an identifier read from input stream
     * First character is already read into ch
     */
    private String getString( )
    {
        String tmpString = "";
             
        for( tmpString += ch; nextChar( ); tmpString += ch )
            if( !isIdChar( ch ) )
            {
                putBackChar( );
                break;
            }

        return tmpString; 
    }

    /**
     * Return next identifier, skipping comments
     * string constants, and character constants.
     * Place identifier in currentIdNode.word and return false
     * only if end of stream is reached.
     */
    private boolean getNextID( )
    {
        while( nextChar( ) )
        {
            if( ch == '/' )
                processSlash( );
            else if( ch == '\\' )
                nextChar( );
            else if( ch == '\'' || ch == '"' )
                skipQuote( ch );
            else if( !Character.isDigit( ch ) && isIdChar( ch ) )
            {
                currentIdNode.word = getString( );
                return true;
            }
        }
        return false;       // End of file
    }


    /**
     * Output the cross reference.
     */
    public void generateCrossReference( )
    {
        BinarySearchTree theIdentifiers = new BinarySearchTree( );

            // Insert identifiers into the search tree
        while( getNextID( ) )
        {
            try
            {
                IdNode thisNode = (IdNode) theIdentifiers.find( currentIdNode );
                thisNode.lines.enqueue( new Integer( currentLine ) );
            }
            catch( ItemNotFound e )
            {
                try
                {
                    theIdentifiers.insert(
                           new IdNode( currentIdNode.word, currentLine ) );
                }
                catch( DuplicateItem ex ) { } // Cannot happen
            }
        }

            // Iterate through search tree and output
            // identifiers and their line number
        try
        {
            InOrder itr = new InOrder( theIdentifiers );
            for( itr.first( ); itr.isValid( ); itr.advance( ) )
            {
                IdNode thisNode = (IdNode) itr.retrieve( );

                    // Print identifier and first line where it occurs
                System.out.print( thisNode.word + ": " +
                                  (Integer) thisNode.lines.dequeue( ) );

                    // Print all other lines on which it occurs
                while( !thisNode.lines.isEmpty( ) )
                    System.out.print( ", " + (Integer) thisNode.lines.dequeue( ) );
                System.out.println( );
            }
        }
        catch( ItemNotFound e ) { }   // Empty tree
        catch( Underflow e ) { }      // Cannot happen
    }

    /**
     * main routine to generate cross-reference
     */
    public static void main( String [ ] args )
    {

        JavaAnalyzer p;

        if( args.length == 0 )
        {

            p = new JavaAnalyzer( new PushbackReader( new InputStreamReader( System.in ) ) );
            p.generateCrossReference( );

            return;
        }

        for( int i = 0; i < args.length; i++ )
        {
            try 
            {
                FileReader f = new FileReader( args[ i ] );

                System.out.println( args[ i ] + ": " );
                p = new JavaAnalyzer( new PushbackReader( f ) );
                p.generateCrossReference( );
                f.close( );
            }
            catch( IOException e )
              { System.err.println( e + args[ i ] ); }

        }
    }
}
