/*
 * GenerateSource.
 * Generates source code for multiple API's from a single source tree using
 * #ifdef, #else, #endif tags.
 *
 * Copyright (C) 1999  Paul Siegmann <pauls@euronet.nl>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

import java.io.*;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * GenerateSource: generate java source code for multiple API's from
 * a single source tree using #ifdef, #else, #endif tags.
 * <BR>
 * This class generates the source code for a particular API version from
 * java source code which has been "enriched" with several tags.<BR>
 * <B>How to use the program:</B><BR>
 * From usage:<BR>
 * usage: GenerateSource -d output_directory -t tagname1 -t tagnameN [source files]<BR>
 * <DL>
 * <DT>-d output_directory
 * <DD>A required parameter, the directory where the resulting files should be written to.<BR>
 * <B>Note:</B> The current version will happily accept "." as output directory
 * or the same sourcefile as targetfile, thus overwriting all your files.<BR>
 * High on the TODO list.<DD>
 * <DT>-t tagname
 * <DD>Define a tag this way. Multiple tags are defined using multiple -t switches.<BR>
 * Valid tags start with A-Za-z and after that any number of A-Za-z0-9_ chars.<BR>
 * </DL>
 * <P>
 * <B>Using tags in the sourcecode:</B><BR>
 * Currently understood tags are:
 * <DL>
 * <DT>#ifdef &lt;tagname&gt; &lt;more tagnames&gt;
 * <DD>every line after the #ifdef line is only written if <EM>any</EM> of
 * the tagnames are defined.<BR>
 * Valid tags start with A-Za-z and after that any number of A-Za-z0-9_ chars.<BR>
 * Parsing the tagline will stop at the first invalid tag.<BR>
 * So a line like: "#ifdef VERSION_2_1 // only do this in VERSION_2_1" will
 * be read as "#ifdef VERSION_2_1".<BR>
 * Note that '=' as the begin of a tagName is reserved for future use.<BR>
 * </DD>
 * <DT>#else
 * <DD>with this tag the writeStatus is negated.<BR>
 * If the program was currently writing lines based on the previous #ifdef
 * condition, it will stop writing.<BR>
 * If on the other hand the program was currently <EM>not</EM> writing lines
 * based on the previous #ifdef condition, it will start doing so.</DD>
 * <DT>#endif
 * <DD>The effect of the previous #ifdef line will be undone</DD>
 * <P>
 * <DT>Example use:</DT>
 * <DD>
 * <PRE>
 * #ifdef TagName_1_1 TagName_1_2 // If either of TagName_1_1 or TagName_1_2
 *                                // is defined writing will start now
 *   ...
 *
 * #ifdef TagName_2_1             // from now on (TagName_1_1 or Tagname_1_2)
 *                                // and TagName_2_1 must be defined
 *  ...
 *
 * #else                          // from now on (TagName_1_1 or Tagname_1_2)
 *                                // and not TagName_2_1 must be defined
 *  ...
 *
 * #endif                         // If either of TagName_1_1 or TagName_1_2 is
 *                                // defined writing will take place
 * </PRE>
 * <DD>
 * </DL>
 * <P>
 * Remarks:
 * <UL>
 * <LI>Tags start with a '#' and may be preceded with spaces and tabs.<BR>
 * In fact anything lower than &#92;u0020 is ignored.
 * <LI>Lines starting with unknown tags are written.<BR>
 * Example: The line "#helloworld ..." is written as "#helloworld..."
 * <LI>When a known tag is preceded with an extra '#' the line is written
 * with the extra '#' removed.<BR>
 * Example: A line starting with ##endif... is written as #endif...
 * <LI>Tags within comments are used<BR>
 * <DL>
 * <DT>Example:
 * <DD>
 * <PRE>
 * &#47;**
 *  #ifdef VERSION_2_1
 *  * &#64;deprecated
 *  #else // VERSION_2_0
 *  * A hot new method
 *  #endif
 *  *&#47;
 * </PRE>
 * <DD>
 * <DT>if VERSION_2_1 is defined this is written as:
 * <DD><PRE>&#64;deprecated</PRE></DD>
 * <DT>And if not:
 * <DD>
 * <PRE>
 * A hot new method
 * </PRE>
 * </DD>
 * <DT>The reason for this is that sometimes a new api version puts a &#64;deprecated tag withing a comment.<BR>
 * This is also one of the reasons this class was written instead of just using cpp.
 * </DL>
 * </UL>
 */
public class GenerateSource {
	final public static String VALID_TAG_CHAR_BEGIN = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	final public static String VALID_TAG_CHAR = VALID_TAG_CHAR_BEGIN + "01234567890_";
	private Vector sourceFiles;
	private String targetDirName = null;
	private boolean myWriteStatus;
	private BufferedReader in;
	private BufferedWriter out;
	private Hashtable tagTable;
	private int lineNr;
	private String tagSet = "";
	private Vector activeTags;

	public static void main(String[] args) {
		try {
			GenerateSource gs = new GenerateSource(args);
			gs.execute();
		} catch(IllegalArgumentException e) {
			GenerateSource.usage();
		}
	}

	
	public GenerateSource(String[] args) throws IllegalArgumentException {
		sourceFiles = new Vector();
		tagTable = new Hashtable();
		processArgs(args);
	}

	public static void usage() {
		System.out.println("usage: GenerateSource");
		System.out.println("\t-d <directory>      Destination directory for output files");
		System.out.println("\t-t <tagname>        A tagname");
	}


	private void processArgs(String[] args) throws IllegalArgumentException {
		try {
			for(int i = 0; i < args.length; i++) {
				if(args[i].equals("-d")) {
					i++;
					targetDirName = args[i]; 

					if(targetDirName.charAt(targetDirName.length()-1) != File.separatorChar) {
						targetDirName += File.separatorChar;
					}
				} else if(args[i].equals("-t")) {
					i++;
					tagTable.put(args[i],"");
					tagSet += " " + args[i];
				} else {
					sourceFiles.addElement(args[i]);
				}
			}
		} catch(ArrayIndexOutOfBoundsException e) {
			throw new IllegalArgumentException();
		}
		if(targetDirName == null) {
			throw new IllegalArgumentException();
		}
	}
	
	public void execute() {
		activeTags = new Vector();
		String inFileName;
		String outFileName = null;
		for(int i = 0; i < sourceFiles.size(); i++) {
			myWriteStatus = true;
			inFileName = (String)(sourceFiles.elementAt(i));
			try {
				in = new BufferedReader(new FileReader(inFileName));
				outFileName = createOutFileName(inFileName);
				out = new BufferedWriter(new FileWriter(outFileName));
				writeGeneratedMessage(out,inFileName);
				System.out.println(inFileName + " => " + outFileName);
				String line = in.readLine();
				while(line != null) {
					lineNr++;
					if(hasTag(line)) {
						processTagLine(line);
					} else if(writeStatus()) {
						writeLine(line);
					}
					line = in.readLine();
				}
				out.flush();
				out.close();
			} catch(FileNotFoundException e) {
				System.err.println("File not found: \"" + inFileName + "\"");
			} catch(IOException f) {
				System.err.println("IOError in file \"" + inFileName + "\" or \"" + outFileName +  "\" :" + f);
			}
		}
	}

	
	private void writeGeneratedMessage(BufferedWriter out, String inFileName) throws IOException {
		out.write(
	"////////////////////////////////////////////////////////////////////////////");
		out.newLine();
		out.write("//\tGENERATED FILE");
		out.newLine();
		out.write("//\tINPUT FILE: " + inFileName);
		out.newLine();
		// tagSet begins with a ' '
		out.write("//\tTAGS USED:" + tagSet);
		out.newLine();
		out.write("//");
		out.newLine();
		out.write("//\tDO NOT EDIT!");
		out.newLine();
		out.write(
	"////////////////////////////////////////////////////////////////////////////");
		out.newLine();
		out.newLine();
	}


	private void writeLine(String line) throws IOException {
		out.write(line,0,line.length());
		out.newLine();
	}


	private String createOutFileName(String inFileName) {
		String outFileName = inFileName;
		try {
			outFileName = inFileName.substring(inFileName.lastIndexOf(File.separatorChar)+1);
		} catch(StringIndexOutOfBoundsException e) {
		}
		return targetDirName + outFileName;
	}


	private void processTagLine(String line) throws IOException {
		StringTokenizer st = new StringTokenizer(line);
		String tag = st.nextToken();
		// System.out.println("tag: " + tag);
		if(tag.equals("#endif")) {
			try {
				activeTags.removeElementAt(activeTags.size() - 1);
			} catch(ArrayIndexOutOfBoundsException e) {
			}
			myWriteStatus = checkWriteStatus();
		} else if(tag.equals("#ifdef")) {
			if(! st.hasMoreTokens()) {
				System.err.println("illegal tag in line " + lineNr);
			}
			Vector tagNames = new Vector();
			String tagName;
			while(st.hasMoreTokens()) {
				tagName = st.nextToken();
				if(VALID_TAG_CHAR_BEGIN.indexOf(tagName.charAt(0)) != -1)
				{
					tagNames.addElement(tagName);
				//	System.out.println("tagName: " + tagName);
				} else {
					break;
				}
			}
			String[] result = new String[tagNames.size()];
			tagNames.copyInto(result);
			activeTags.addElement(result);
			myWriteStatus = checkWriteStatus();
		} else if(tag.equals("#else")) {
			myWriteStatus = checkWriteStatus(true);
		} else if((tag.equals("##endif"))
		       ||(tag.equals("##ifdef"))
		       ||(tag.equals("##else"))) {
			writeLine(line.substring(0,line.indexOf('#')) + line.substring(line.indexOf('#') + 1));
		} else {
			writeLine(line);
		}
	}


	/**
	 * checks whether every String[] in activeTags contains at least
	 * a defined tag.
	 */
	private boolean checkWriteStatus() {
		return checkWriteStatus(false);
	}


	private boolean checkWriteStatus(boolean elseTag) {
		String[] currentTagSet;
		boolean foundIt;
		int checkSize;

		if(elseTag) {
			checkSize = activeTags.size()-1;
		} else {
			checkSize = activeTags.size();
		}
		for(int i = 0; i < checkSize; i++) {
			foundIt = false;
			currentTagSet = (String[])activeTags.elementAt(i);
			for(int j = 0; j < currentTagSet.length; j++) {
				if(tagTable.containsKey(currentTagSet[j])) {
					foundIt = true;
					break;
				}
			}
			if(foundIt == false) {
				return false;
			}
		}
		if(elseTag) {
			currentTagSet = (String[])activeTags.lastElement();
			for(int j = 0; j < currentTagSet.length; j++) {
				if(tagTable.containsKey(currentTagSet[j])) {
					return false;
				}
			}
		}
		return true;
	}


	private boolean writeStatus() {
		return myWriteStatus;
	}


	private boolean hasTag(String line) {
		for(int i = 0; i < line.length(); i++) {
			if(line.charAt(i) == '#') {
				return true;
			} else if(line.charAt(i) > '\u0020') {
				break;
			}
		}
		return false;
	}
}
