/* Copyright (c) 2008, Paul Cager.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.javacc.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Generates boiler-plate files from templates. Only very basic
* template processing is supplied - if we need something more
* sophisticated I suggest we use a third-party library.
*
* @author paulcager
* @since 4.2
*/
public class JavaFileGenerator {
/**
* @param templateName the name of the template. E.g.
* "/templates/Token.template".
* @param options the processing options in force, such
* as "STATIC=yes"
*/
public JavaFileGenerator(String templateName, Map options) {
this.templateName = templateName;
this.options = options;
}
private final String templateName;
private final Map options;
private String currentLine;
/**
* Generate the output file.
* @param out
* @throws IOException
*/
public void generate(PrintWriter out) throws IOException
{
System.out.println(templateName);
InputStream is = getClass().getResourceAsStream(templateName);
if (is == null)
throw new IOException("Invalid template name: " + templateName);
BufferedReader in = new BufferedReader(new InputStreamReader(is));
process(in, out, false);
}
String[] getResourceListing(Class clazz, String path) throws URISyntaxException, IOException {
URL dirURL = clazz.getClassLoader().getResource(path);
if (dirURL != null && dirURL.getProtocol().equals("file")) {
/* A file path: easy enough */
return new File(dirURL.toURI()).list();
}
if (dirURL == null) {
/*
* In case of a jar file, we can't actually find a directory.
* Have to assume the same jar as clazz.
*/
String me = clazz.getName().replace(".", "/")+".class";
dirURL = clazz.getClassLoader().getResource(me);
}
if (dirURL.getProtocol().equals("jar")) {
/* A JAR path */
String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory
while(entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.startsWith(path)) { //filter according to the path
String entry = name.substring(path.length());
int checkSubdir = entry.indexOf("/");
if (checkSubdir >= 0) {
// if it is a subdirectory, we just return the directory name
entry = entry.substring(0, checkSubdir);
}
result.add(entry);
}
}
return result.toArray(new String[result.size()]);
}
throw new UnsupportedOperationException("Cannot list files for URL "+dirURL);
}
private String peekLine(BufferedReader in) throws IOException
{
if (currentLine == null)
currentLine = in.readLine();
return currentLine;
}
private String getLine(BufferedReader in) throws IOException
{
String line = currentLine;
currentLine = null;
if (line == null)
in.readLine();
return line;
}
private boolean evaluate(String condition)
{
condition = condition.trim();
try
{
return new ConditionParser(new StringReader(condition)).CompilationUnit(options);
}
catch (ParseException e)
{
return false;
}
}
private String substitute(String text) throws IOException
{
int startPos;
if ( (startPos = text.indexOf("${")) == -1)
{
return text;
}
// Find matching "}".
int braceDepth = 1;
int endPos = startPos + 2;
while ( endPos < text.length() && braceDepth > 0)
{
if (text.charAt(endPos) == '{')
braceDepth++;
else if (text.charAt(endPos) == '}')
braceDepth--;
endPos++;
}
if (braceDepth != 0)
throw new IOException("Mismatched \"{}\" in template string: " + text);
final String variableExpression = text.substring(startPos + 2, endPos - 1);
// Find the end of the variable name
String value = null;
for (int i = 0; i < variableExpression.length(); i++)
{
char ch = variableExpression.charAt(i);
if (ch == ':' && i < variableExpression.length() - 1 && variableExpression.charAt(i+1) == '-' )
{
value = substituteWithDefault(variableExpression.substring(0, i), variableExpression.substring(i + 2));
break;
}
else if (ch == '?')
{
value = substituteWithConditional(variableExpression.substring(0, i), variableExpression.substring(i + 1));
break;
}
else if (ch != '_' && !Character.isJavaIdentifierPart(ch))
{
throw new IOException("Invalid variable in " + text);
}
}
if (value == null)
{
value = substituteWithDefault(variableExpression, "");
}
return text.substring(0, startPos) + value + text.substring(endPos);
}
/**
* @param substring
* @param defaultValue
* @return
* @throws IOException
*/
private String substituteWithConditional(String variableName, String values) throws IOException
{
// Split values into true and false values.
int pos = values.indexOf(':');
if (pos == -1)
throw new IOException("No ':' separator in " + values);
if (evaluate(variableName))
return substitute(values.substring(0, pos));
else
return substitute(values.substring(pos + 1));
}
/**
* @param variableName
* @param defaultValue
* @return
*/
private String substituteWithDefault(String variableName, String defaultValue) throws IOException
{
Object obj = options.get(variableName.trim());
if (obj == null || obj.toString().length() == 0)
return substitute(defaultValue);
return obj.toString();
}
private void write(PrintWriter out, String text) throws IOException
{
while ( text.indexOf("${") != -1)
{
text = substitute(text);
}
out.println(text);
}
private void process(BufferedReader in, PrintWriter out, boolean ignoring) throws IOException
{
// out.println("*** process ignore=" + ignoring + " : " + peekLine(in));
while ( peekLine(in) != null)
{
if (peekLine(in).trim().startsWith("#if"))
{
processIf(in, out, ignoring);
}
else if (peekLine(in).trim().startsWith("#"))
{
break;
}
else
{
String line = getLine(in);
if (line.startsWith("\\#")) { // Hack to escape # for C++
line = line.substring(1);
}
if (!ignoring) write(out, line);
}
}
out.flush();
}
private void processIf(BufferedReader in, PrintWriter out, boolean ignoring) throws IOException
{
String line = getLine(in).trim();
assert line.trim().startsWith("#if");
boolean foundTrueCondition = false;
boolean condition = evaluate(line.substring(3).trim());
while (true)
{
process(in, out, ignoring || foundTrueCondition || !condition);
foundTrueCondition |= condition;
if (peekLine(in) == null || !peekLine(in).trim().startsWith("#elif"))
break;
condition = evaluate(getLine(in).trim().substring(5).trim());
}
if (peekLine(in) != null && peekLine(in).trim().startsWith("#else"))
{
getLine(in); // Discard the #else line
process(in, out, ignoring || foundTrueCondition);
}
line = getLine(in);
if (line == null)
throw new IOException("Missing \"#fi\"");
if (!line.trim().startsWith("#fi"))
throw new IOException("Expected \"#fi\", got: " + line);
}
public static void main(String[] args) throws Exception
{
Map map = new HashMap();
map.put("falseArg", Boolean.FALSE);
map.put("trueArg", Boolean.TRUE);
map.put("stringValue", "someString");
new JavaFileGenerator(args[0], map).generate(new PrintWriter(args[1]));
}
}