/*
 * 	
 * 
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */
import java.io.*;


/**
 * A simple Java preprocessor.  It process the following kind of directives
 * in Java code:
 *    \/\* #ifdef <LABEL> \*\/
 *    \/\* #ifndef <LABEL> \*\/
 *    \/\* #else \*\/
 *    \/\* #endif \*\/
 * The directives have to be embedded in a comment.
 */
public class Jpp {

    FileInputStream input;
    FileOutputStream output;

    static String IFDEF = "#ifdef";
    static String IFNDEF = "#ifndef";
    static String ELSE = "#else";
    static String ENDIF = "#endif";

    String labels[];
    int nLabels = 0;
    boolean space = true;
    boolean trace = false;

    public static void main(String [] args) {
	if (args.length < 1)
	    prUsage();

	String input = null;
	String labels[] = new String[256];
	String destDir = null, destFile = null;
	int n = 0;
	boolean space = true;
	boolean trace = false;

	int i = 0;
	while (i < args.length) {
	    if (args[i].startsWith("-D")) {
		labels[n++] = args[i].substring(2);
	    } else if (args[i].equals("-d")) {
		if (i == args.length)
		    prUsage();
		i++;
		destDir = args[i];	
	    } else if (args[i].equals("-o")) {
		if (i == args.length)
		    prUsage();
		i++;
		destFile = args[i];	
	    } else if (args[i].equals("-nospace")) {
		space = false;
	    } else if (args[i].equals("-trace")) {
		trace = true;
	    } else {
		if (args[i].endsWith(".jpp"))
		    input = args[i];
	    }
	    i++;
	}

	if (input == null)
	    prUsage();

	if (destDir != null && destFile != null)
	    prUsage();

	Jpp jpp = new Jpp();
	jpp.process(input, labels, n, destDir, destFile, space, trace);
    }


    static void prUsage() {
	System.err.println(
	    "java Jpp <input>.jpp -D<label1> ... -D<labeln> " +
	    "[-d <output directory>] [-o <output file>] [-nospace] [-trace]");
	System.exit(-1);
    }


    void process(String inFile, String labels[], int nLabels, String destDir, 
	String destFile, boolean space, boolean trace) {

	this.labels = labels;
	this.nLabels = nLabels;
	this.space = space;
	this.trace = trace;

	String outFile;
	String separator = System.getProperty("file.separator");

	if (destFile != null) {
	    try {
		int idx = destFile.lastIndexOf(separator);
		if (idx != -1) {
		    String dir = destFile.substring(0, idx);
		    File f = new File(dir);
		    f.mkdirs();
		}
	    } catch (Exception e) {
		System.err.println(e);
	    }
	    outFile = destFile;
	} else if (destDir != null) {
	    int idx = inFile.lastIndexOf(separator);
	    if (idx != -1)
		outFile = inFile.substring(idx+1, inFile.length()-3) + "java";
	    else
		outFile = inFile.substring(0, inFile.length()-3) + "java";
	    outFile = destDir + separator + outFile;
	} else
	    outFile = inFile.substring(0, inFile.length()-3) + "java";

        (new File(outFile)).getParentFile().mkdirs();
        
	try {
	    input = new FileInputStream(inFile);
	} catch (IOException e) {
	    System.err.println("Cannot open input file: " + inFile + ":\n" + e);
            System.exit(-1);
	}

	try {
	    output = new FileOutputStream(outFile);
	} catch (IOException e) {
	    System.err.println("Cannot create output file: " + outFile +
                               ":\n" + e);
            System.exit(-1);
	}

	try {
	    processCode(true, false, null, 0);
	} catch (IOException e) {
	    System.err.println(e);
	    System.exit(-1);
	}
    }


    boolean processCode(boolean include, boolean endif,
		        String token, int level) 
	throws IOException {
	byte bary[] = new byte[1024];
	int b;
	int commentCount = 0;
	int idx = 0;
	int bidx[] = new int[1];
	byte cr[] = new byte[1];

	while (true) {
	    b = input.read();
	    if (b < 0)
		break;


	    if ((b == '/' || b == '*') && commentCount == 1) {
		bary[idx++] = (byte)b;
		// Found the comment block.

		bidx[0] = idx;
		cr[0] = -1;
		String tk = getToken(bary, bidx, cr);
		if (tk == null)
		    break;
		idx = bidx[0];

		if (tk.equals(IFDEF) || tk.equals(IFNDEF)) {
		    bidx[0] = idx;
		    cr[0] = -1;
		    String tk2 = getToken(bary, bidx, cr);
		    if (tk2 == null)
			break;
		    idx = bidx[0];

		    skipComments(b == '/', cr[0]);

		    boolean elseFound;
		    boolean incl;

		    if (trace)
			trace(tk, tk2, level+1);

		    if (include) {
			if ((tk.equals(IFDEF) && isDefined(tk2)) ||
			    (tk.equals(IFNDEF) && !isDefined(tk2))) {
			    incl = true;
			} else {
			    incl = false;
			}
			elseFound = processCode(incl, true, tk2, level+1);
			if (elseFound)
			    elseFound = processCode(!incl, true, tk2, level+1);
		    } else {
			elseFound = processCode(false, true, tk2, level+1);
			if (elseFound)
			    elseFound = processCode(false, true, tk2, level+1);
		    }
		    if (elseFound)
			throw new IOException("Unmatched #else");
		    idx = 0;
		} else if (tk.equals(ELSE)) {
		    if (trace)
			trace(tk, "for " + token, level);
		    if (!endif)
			throw new IOException("Unmatched #else");
		    skipComments(b == '/', cr[0]);
		    idx = 0;
		    return true;
		} else if (tk.equals(ENDIF)) {
		    if (trace)
			trace(tk, "for " + token, level);
		    if (!endif)
			throw new IOException("Unmatched #endif");
		    skipComments(b == '/', cr[0]);
		    idx = 0;
		    return false;
		} else {
		    // No match.
		    // Write the code if it should be included.
		    // Otherwise, only write the CR to maintain
		    // the line numbers.
		    if (include)
			output.write(bary, 0, idx);
		    else if (space)
			replaceCR(bary, 0, idx);
		    idx = 0;
		}
		commentCount = 0;
	    } else if (b == '/') {
		commentCount = 1;
		bary[idx++] = (byte)b;
	    } else { 
		if (commentCount != 0) {
		    // Write the code if it should be included.
		    // Otherwise, only write the CR to maintain
		    // the line numbers.
		    if (include)
			output.write(bary, 0, idx);
		    else if (space)
			replaceCR(bary, 0, idx);
		    idx = 0;
		    commentCount = 0;
		}
		if (include)
		    output.write(b);
		else if (space && (b == 10 || b == 13))
		    output.write(b);
	    }
	}

	if (endif)
	    throw new IOException("Missing #endif");

	return false;
    }


    void skipComments(boolean slash, byte cr) throws IOException {
	int b;

	// Handle double slash type of comments.
	if (slash) {
	    if (cr != -1) {
		if (space)
		    output.write(cr);
	        return;
	    }
	    while (true) {
		b = input.read();
		if (b < 0)
		    break;
		if (b == 10 || b == 13) {
		    if (space)
			output.write(b);
		    break;
		}
	    }
	    return;
	}

	int commentCount = 0;

	while (true) {
	    b = input.read();
	    if (b < 0)
		throw new IOException("Incomplete comments block");

	    if (b == '*') {
		commentCount = 1;
	    } else if (b == '/' && commentCount == 1) {
		commentCount = 0;
		break;
	    } else {
		if (space && (b == 10 || b == 13))
		    output.write(b);
		commentCount = 0;
	    }
	}
    }


    /**
     * Parse the characters to only write te CR's.
     */
    void replaceCR(byte b[], int off, int len) throws IOException {
	for (int i = 0; i < len; i++) {
	    if (space && (b[off+i] == 10 || b[off+i] == 13))
		output.write(b[off+i]);
	}
    }


    /**
     * Extract the next token.
     */
    byte token[] = new byte[1024];

    String getToken(byte bary[], int idx[], byte cr[]) throws IOException {
	int b;
	int i = 0;

	// Discard the leading spaces.
	do {
	    b = input.read();
	    if (b < 0)
		break;
	    bary[idx[0]++] = (byte)b;
	} while (b == ' ' || b == '\t' || b == '*' || b == '/' || 
		b == 10 || b == 13);

	while (true) {
	    if (b == ' ' || b == '\t' || b == 10 || b == 13) {
		if (cr != null && (b == 10 || b == 13))
		    cr[0] = (byte)b;
		break;
	    } else
		token[i++] = (byte)b;

	    b = input.read();
	    if (b < 0)
		break;
	    bary[idx[0]++] = (byte)b;
	}

	if (i == 0)
	    return null;

	return new String(token, 0, i);
    }


    /**
     * Check if the label is defined.
     */
    boolean isDefined(String label) {
	for (int i = 0; i < nLabels; i++) {
	    if (label.equals(labels[i]))
		return true;
	}
	if (System.getProperty(label) != null)
	    return true;
	return false;
    }

    void trace(String tk1, String tk2, int level) {
	for (; level > 0; level--)
	    System.out.print("  ");
	System.out.println(tk1 + " " + tk2);
    }
}

