/* * The contents of this file are subject to the terms of the Common Development * and Distribution License (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at http://www.netbeans.org/cddl.html * or http://www.netbeans.org/cddl.txt. * * When distributing Covered Code, include this CDDL Header Notice in each file * and include the License file at http://www.netbeans.org/cddl.txt. * If applicable, add the following below the CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. */ import java.io.*; import java.util.*; /** * Munge: a purposely-simple Java preprocessor. It only * supports conditional inclusion of source based on defined strings of * the form "if[tag]", * "if_not[tag]", "else[tag], and "end[tag]". Unlike traditional * preprocessors, comments and formatting are all preserved for the * included lines. This is on purpose, as the output of Munge * will be distributed as human-readable source code. *

* To avoid creating a separate Java dialect, the conditional tags are * contained in Java comments. This allows one build to compile the * source files without pre-processing, to facilitate faster incremental * development. Other builds from the same source have their code contained * within that comment. The format of the tags is a little verbose, so * that the tags won't accidentally be used by other comment readers * such as javadoc. Munge tags must be in C-style comments; * C++-style comments may be used to comment code within a comment. * *

* To demonstrate this, our sample source has 1.1 and 1.2-specific code, * with 1.1 as the default build: *


 *     public void setSystemProperty(String key, String value) {
 *         /*if[JDK1.1]*/
 *         Properties props = System.getProperties();
 *         props.setProperty(key, value);
 *         System.setProperties(props);
 *         /*end[JDK1.1]*/
 * 

* /*if[JDK1.2] * // Use the new System method. * System.setProperty(key, value); * end[JDK1.2]*/ * } *

*

* When the above code is directly compiled, the code bracketed by * the JDK1.1 tags will be used. If the file is run through * Munge with the JDK1.2 tag defined, the second code block * will used instead. This code can also be written as: *


 *     public void setSystemProperty(String key, String value) {
 *         /*if[JDK1.2]
 *         // Use the new System method.
 *         System.setProperty(key, value);
 *           else[JDK1.2]*/
 * 

* Properties props = System.getProperties(); * props.setProperty(key, value); * System.setProperties(props); * /*end[JDK1.2]*/ * } *

* * Munge also performs text substitution; the Swing build uses this to * convert its package references from javax.swing * to java.awt.swing, for example. This substitution is * has no knowledge of Java syntax, so only use it to convert strings * which are unambiguous. Substitutions are made in the same order as * the arguments are specified, so the first substitution is made over * the whole file before the second one, and so on. *

* Munge's command line takes one of the following forms: *


 *    java Munge [-D<symbol> ...] [-s <old>=<new> ...] [<in file>] [<out file>]
 *    java Munge [-D<symbol> ...] [-s <old>=<new> ...] <file> ... <directory>
 * 
*

* In the first form, if no output file is given, System.out is used. If * neither input nor output file are given, System.in and System.out are used. * Munge can also take an @<cmdfile> argument. If one is * specified then the given file is read for additional command line arguments. *

* Like any preprocessor, developers must be careful not to abuse its * capabilities so that their code becomes unreadable. Please use it * as little as possible. * * @author: Thomas Ball * @version: 1.7 98/10/13 */ public class Munge { static Hashtable symbols = new Hashtable(2); static Vector oldTextStrings = new Vector(); static Vector newTextStrings = new Vector(); int errors = 0; int line = 1; String inName; BufferedReader in; PrintWriter out; Stack stack = new Stack(); boolean printing = true; String source = null; String block = null; final String[] commands = { "if", "if_not", "else", "end" }; final int IF = 0; final int IF_NOT = 1; final int ELSE = 2; final int END = 3; final int numCommands = 4; final int EOF = 0; final int COMMENT = 1; // text surrounded by /* */ delimiters final int CODE = 2; // can just be whitespace int getCommand(String s) { for (int i = 0; i < numCommands; i++) { if (s.equals(commands[i])) { return i; } } return -1; } public void error(String text) { System.err.println("File " + inName + " line " + line + ": " + text); errors++; } public void printErrorCount() { if (errors > 0) { System.err.println(Integer.toString(errors) + (errors > 1 ? " errors" : " error")); } } public boolean hasErrors() { return (errors > 0); } public Munge(String inName, String outName) { this.inName = inName; if( inName == null ) { in = new BufferedReader( new InputStreamReader(System.in) ); } else { try { in = new BufferedReader( new FileReader(inName) ); } catch (FileNotFoundException fnf) { System.err.println("Cannot find input file " + inName); errors++; return; } } if( outName == null ) { out = new PrintWriter(System.out); } else { try { out = new PrintWriter( new FileWriter(outName) ); } catch (IOException ioe) { System.err.println("Cannot write to file " + outName); errors++; } } } public void close() throws IOException { in.close(); out.flush(); out.close(); } void cmd_if(String version) { Boolean b = new Boolean(printing); stack.push(b); printing = (symbols.get(version) != null); } void cmd_if_not(String version) { Boolean b = new Boolean(printing); stack.push(b); printing = (symbols.get(version) == null); } void cmd_else() { printing = !printing; } void cmd_end() throws EmptyStackException { Boolean b = (Boolean)stack.pop(); printing = b.booleanValue(); } void print(String s) throws IOException { if (printing) { out.write(s); } else { // Output empty lines to preserve line numbering. int n = countLines(s); for (int i = 0; i < n; i++) { out.write('\n'); } } } // Return the number of line endings in a string. int countLines(String s) { int i = 0; int n = 0; while ((i = block.indexOf('\n', i) + 1) > 0) { n++; } return n; } /* * If there's a preprocessor tag in this comment, act on it and return * any text within it. If not, just return the whole comment unchanged. */ void processComment(String comment) throws IOException { String commentText = comment.substring(2, comment.length() - 2); StringTokenizer st = new StringTokenizer( commentText, "[] \t\r\n", true); boolean foundTag = false; StringBuffer buffer = new StringBuffer(); try { while (st.hasMoreTokens()) { String token = st.nextToken(); int cmd = getCommand(token); if (cmd == -1) { buffer.append(token); if (token.equals("\n")) { line++; } } else { token = st.nextToken(); if (!token.equals("[")) { // Not a real tag: save it and continue... buffer.append(commands[cmd]); buffer.append(token); } else { String symbol = st.nextToken(); if (!st.nextToken().equals("]")) { error("invalid preprocessor statement"); } foundTag = true; // flush text, as command may change printing state print(buffer.toString()); buffer.setLength(0); // reset buffer switch (cmd) { case IF: cmd_if(symbol); break; case IF_NOT: cmd_if_not(symbol); break; case ELSE: cmd_else(); break; case END: cmd_end(); break; default: throw new InternalError("bad command"); } } } } } catch (NoSuchElementException nse) { error("invalid preprocessor statement"); } catch (EmptyStackException ese) { error("unmatched end or else statement"); } if (foundTag) { print(buffer.toString()); } else { print(comment); } } // Munge views a Java source file as consisting of // blocks, alternating between comments and the text between them. int nextBlock() throws IOException { if (source == null || source.length() == 0) { block = null; return EOF; } if (source.startsWith("/*")) { // Return comment as next block. int i = source.indexOf("*/"); if (i == -1) { // malformed comment, skip block = source; return CODE; } i += 2; // include comment close block = source.substring(0, i); source = source.substring(i); return COMMENT; } // Return text up to next comment, or rest of file if no more comments. int i = source.indexOf("/*"); if (i != -1) { block = source.substring(0, i); source = source.substring(i); } else { block = source; source = null; } // Update line count -- this isn't done for comments because // line counting has to be done during parsing. line += countLines(block); return CODE; } void substitute() { for (int i = 0; i < oldTextStrings.size(); i++) { String oldText = (String)oldTextStrings.elementAt(i); String newText = (String)newTextStrings.elementAt(i); int n; while ((n = source.indexOf(oldText)) >= 0) { source = source.substring(0, n) + newText + source.substring(n + oldText.length()); } } } public void process() throws IOException { // Read all of file into a single stream for easier scanning. StringWriter sw = new StringWriter(); char[] buffer = new char[8192]; int n; while ((n = in.read(buffer, 0, 8192)) > 0) { sw.write(buffer, 0, n); } source = sw.toString(); // Perform any text substitutions. substitute(); // Do preprocessing. int blockType; do { blockType = nextBlock(); if (blockType == COMMENT) { processComment(block); } else if (blockType == CODE) { print(block); } } while (blockType != EOF); // Make sure any conditional statements were closed. if (!stack.empty()) { error("missing end statement(s)"); } } /** * Report how this utility is used and exit. */ public static void usage() { System.err.println("usage:" + "\n java Munge [-D ...] " + "[-s = ...] " + "[] []" + "\n java Munge [-D ...] " + "[-s = ...] " + " ... " ); System.exit(1); } public static void usage(String msg) { System.err.println(msg); usage(); } /** * Munge's main entry point. */ public static void main(String[] args) { // Use a dummy object as the hash entry value. Object obj = new Object(); // Replace and @file arguments with the contents of the specified file. try { args = CommandLine.parse( args ); } catch( IOException e ) { usage("Unable to read @file argument."); } // Load symbol definitions int iArg = 0; while (iArg < args.length && args[iArg].startsWith("-")) { if (args[iArg].startsWith("-D")) { String symbol = args[iArg].substring(2); symbols.put(symbol, obj); } else if (args[iArg].equals("-s")) { if (iArg == args.length) { usage("no substitution string specified for -s parameter"); } // Parse and store = parameter. String subst = args[++iArg]; int equals = subst.indexOf('='); if (equals < 1 || equals >= subst.length()) { usage("invalid substitution string \"" + subst + "\""); } String oldText = subst.substring(0, equals); oldTextStrings.addElement(oldText); String newText = subst.substring(equals + 1); newTextStrings.addElement(newText); } else { usage("invalid flag \"" + args[iArg] + "\""); } ++iArg; } // Parse file name arguments into an array of input file names and // output file names. String[] inFiles = new String[Math.max(args.length-iArg-1, 1)]; String[] outFiles = new String[inFiles.length]; if( iArg < args.length ) { File targetDir = new File( args[args.length-1] ); if( targetDir.isDirectory() ) { int i = 0; for( ; iArg 1 && arg.charAt(0) == '@') { arg = arg.substring(1); if (arg.charAt(0) == '@') { newArgs.addElement(arg); } else { loadCmdFile(arg, newArgs); } } else { newArgs.addElement(arg); } } String[] newArgsArray = new String[newArgs.size()]; newArgs.copyInto(newArgsArray); return newArgsArray; } private static void loadCmdFile(String name, Vector args) throws IOException { Reader r = new BufferedReader(new FileReader(name)); StreamTokenizer st = new StreamTokenizer(r); st.resetSyntax(); st.wordChars(' ', 255); st.whitespaceChars(0, ' '); st.commentChar('#'); st.quoteChar('"'); st.quoteChar('\''); while (st.nextToken() != st.TT_EOF) { args.addElement(st.sval); } r.close(); } } }