/* * 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