package code.generators; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; /** *

* Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights * reserved.
* BSD-style license. See OpenTrafficSim License. *

* $LastChangedDate$, @version $Revision$, by $Author: pknoppers * $, initial version okt. 2014
* @author Peter Knoppers */ public class CodeGenerator { /** Name of this program. */ final String generatorName; /** Directory where the tree of files will be built. */ final String buildDir; /** The serialVersionUID to put in the generated classes. */ final String serialVersionUID; /** Base directory / package. */ final String packageBaseName; /** Description of the day on which the files were generated. */ final String when; /** Name of the package-info file(s). */ final static String packageInfoName = "package-info"; /** * Create a new CodeGenerator. * @param generatorName String; name of the program that uses this CodeGenerator * @param buildDir String; path to directory for generated files and sub-directories * @param packageBaseName String; prepended to all package names * @param when String; textual description of the date that this CodeGenerator is used * @param serialVersionUID String; serialVersionID to put in the generated class files */ public CodeGenerator(final String generatorName, final String buildDir, final String packageBaseName, final String when, final Long serialVersionUID) { this.generatorName = generatorName; this.buildDir = buildDir; this.packageBaseName = packageBaseName; this.when = when; this.serialVersionUID = serialVersionUID.toString(); File testPath = new File(buildDir); if (!testPath.exists()) { throw new Error("buildDir (" + buildDir + ") does not exist"); } else if (!testPath.isDirectory()) { throw new Error("buildDir (" + buildDir + ") is not a directory"); } } /** * Open a file and write the package line and initial block comment. * @param relativePackage String; the last element(s) of the package name * @param name String; the name of the class file to write * @param imports String[]; the imports for the new class file * @param description String; the description that is inserted in the block comment at the start of the file * @param genericParams String[]; descriptions of the generic parameters of the class * @return BufferedWriter; the open file */ public final BufferedWriter openFile(final String relativePackage, final String name, final String[] imports, final String description, final String[] genericParams) { BufferedWriter writer = null; String dirList = relativePackage; String[] intermediateDirs = dirList.split("[\\.]"); String path = this.buildDir; for (String intermediateDir : intermediateDirs) { path = path + File.separatorChar + intermediateDir; } File dir = new File(path); if (!dir.exists() && !dir.mkdirs()) { throw new Error("Cannot create path \"" + path + "\""); } String fileName = path + File.separatorChar + name + ".java"; final String packageLine = "package " + this.packageBaseName + "." + relativePackage + ";\r\n"; try { writer = new BufferedWriter(new FileWriter(new File(fileName))); if (!name.equals(packageInfoName)) { writer.write(packageLine + "\r\n"); } if (null != imports) { for (String importString : imports) { if (null == importString) { continue; } if (importString.length() > 0) { writer.write("import " + importString + ";"); } writer.write("\r\n"); } writer.write("\r\n"); } // Gotcha: make sure the svn keyword substitution does not touch these. writer.write("/**\r\n * " + description + "\r\n *

\r\n * This file was generated by " + this.generatorName + ", " + this.when + "\r\n *

\r\n" + " * Copyright (c) 2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the " + "Netherlands. All rights" + " reserved.
\r\n" + " * BSD-style license. See OpenTrafficSim " + "License.\r\n" + " *

\r\n" + " * $" + "LastChangedDate$, @version $" + "Revision$, by $" + "Author$, initial version" + this.when + "
\r\n" + " * @author Alexander Verbraeck\r\n" + " * @author Peter Knoppers\r\n"); if (null != genericParams) { for (String param : genericParams) { writer.write(" * @param " + param + "\r\n"); } } writer.write(" */\r\n"); if (name.equals(packageInfoName)) { writer.write(packageLine); } } catch (Exception e) { throw new Error("Cannot write file " + fileName); } return writer; } /** * Close a file that was opened with openFile. * @param writer BufferedWriter; the result of the preceding call to openFile */ public static void closeFile(final BufferedWriter writer) { try { writer.close(); } catch (IOException exception) { exception.printStackTrace(); } } /** * Return the String that defines the serialVersionUID. * @param indent String; prepended to each output line * @return String */ public final String buildSerialVersionUID(final String indent) { return buildField(indent, "private static final long serialVersionUID = " + this.serialVersionUID + "L", ""); } /** * Write a class file. * @param relativePackage String; the last element(s) of the package name * @param name String; the name of the class file to write * @param imports String[]; the imports for the new class file * @param description String; the description that is inserted in the block comment at the start of the file * @param genericParams String[]; the descriptions of the generic parameters of the class * @param qualifiers String; qualifiers that go before the class key word; e.g. public abstract * @param typeInfo String; the text that goes immediately after the class name * @param generateSerialVersionUID boolean; if true a serialVersionUID is put in the result * @param contents String; the text that goes in the class */ public final void generateClass(final String relativePackage, final String name, final String[] imports, final String description, final String[] genericParams, final String qualifiers, final String typeInfo, final boolean generateSerialVersionUID, final String contents) { try { BufferedWriter writer = openFile(relativePackage, name, imports, description, genericParams); writer.write(qualifiers + " class " + name + (typeInfo.length() == 0 || typeInfo.startsWith("<") ? "" : " ") + typeInfo + "\r\n{\r\n"); if (generateSerialVersionUID) { writer.write(buildSerialVersionUID(indent(1))); } writer.write(contents + "}\r\n"); closeFile(writer); } catch (Exception e) { e.printStackTrace(); } } /** * Write a class file for an abstract class. * @param relativePackage String; the last element(s) of the package name * @param name String; the name of the class file to write * @param imports String[]; the imports for the new class file * @param description String; the description that is inserted in the block comment at the start of the file * @param genericParams String[]; the descriptions of the generic parameters of the class * @param typeInfo String; the text that goes immediately after the class name * @param contents String; the text that goes in the class */ public final void generateAbstractClass(final String relativePackage, final String name, final String[] imports, final String description, final String[] genericParams, final String typeInfo, final String contents) { generateClass(relativePackage, name, imports, description, genericParams, "public abstract", typeInfo, true, contents); } /** * Write a class file for a final class that cannot be instantiated. * @param relativePackage String; the last element(s) of the package name * @param name String; the name of the class file to write * @param imports String[]; the imports for the new class file * @param description String; the description that is inserted in the block comment at the start of the file * @param genericParams String[]; the descriptions of the generic parameters of the class * @param typeInfo String; the text that goes immediately after the class name * @param contents String; the text that goes in the class */ public final void generateFinalClass(final String relativePackage, final String name, final String[] imports, final String description, final String[] genericParams, final String typeInfo, final String contents) { generateClass( relativePackage, name, imports, description, genericParams, "public final", typeInfo, false, buildMethod(indent(1), "private||" + name, "This class shall never be instantiated.", null, null, null, new String[]{"// Prevent instantiation of this class"}, true) + contents); } /** * Create a String that describes one field. * @param indent String; prepended to each output line * @param field String; the type and name of the field * @param description String; the description of the field * @return String */ public final String buildField(final String indent, final String field, final String description) { return indent + "/** " + description + " */\r\n" + indent + field + ";\r\n\r\n"; } /** * Generate a block comment. * @param indent String; prefix of all output lines * @param comment String; the text to center in the block comment * @return String; java code */ public final String buildBlockComment(final String indent, String comment) { comment = " " + comment + " "; StringBuffer construction = new StringBuffer(); final String pattern = "/**********************************************************************************/"; construction.append(indent + pattern + "\r\n"); int halfTruncate = comment.length() / 2; construction.append(indent + pattern.substring(0, pattern.length() / 2 - halfTruncate)); construction.append(comment); construction.append(pattern.substring(pattern.length() / 2 + comment.length() - halfTruncate)); construction.append("\r\n"); construction.append(indent + pattern + "\r\n\r\n"); return construction.toString(); } /** * Create an indent of N units. * @param steps int; the number N * @return String */ public final String indent(final int steps) { final String indent = " "; String result = ""; for (int i = 0; i < steps; i++) { result += indent; } return result; } /** * Create a String that defines one method. * @param indent String; prefix for all output lines * @param qualifiersTypeAndName String; the qualifiers, the type and the name of the method separated by vertical * bars, e.g. final public|double|getDoubleValue. If the method that must be generated * overrides a method in a parent class set this parameter to null. * @param description String; description of the method * @param parameters String[]; one String for each parameter of the method. Each parameter string consists of * qualifiers, type, name separated by vertical bars, e.g. final int|index|index of the * entry. Null entries in the parameters array are silently ignored. * @param exceptions String; exception type and description separated by a vertical bar, or null if this method does * not throw exceptions * @param pragma String; text that goes after the JavaDoc, but before the start of the method code * @param body String[]; the lines of the body of the method. Lines on the outermost level should start with 0 * spaces. Null entries in the body array are silently ignored * @param constructor boolean; if true; the new method is a constructor; if false; the new method is not a * constructor * @return String; the Java source code of the method. */ public final String buildMethod(final String indent, final String qualifiersTypeAndName, final String description, final String[] parameters, final String exceptions, final String pragma, final String[] body, final boolean constructor) { final int maxLineLength = 121; StringBuilder construction = new StringBuilder(); String[] fields = qualifiersTypeAndName.split("[|]"); if (null != description) { construction.append(indent + "/**\r\n" + indent + " * "); construction.append(description); construction.append("\r\n"); if (null != parameters) { for (String param : parameters) { if (null == param) { continue; } String[] paramFields = param.split("[|]"); if (3 != paramFields.length) { throw new Error("param should consist of three fields separated by |; got \"" + param + "\""); } if (paramFields[0].startsWith("final ")) { paramFields[0] = paramFields[0].substring(6); } String line = indent + " * @param " + paramFields[1] + " " + escapeHTML(paramFields[0]) + ";"; String remainder = paramFields[2]; String[] words = remainder.split("[ ]"); for (String word : words) { if (line.length() + 1 + word.length() >= maxLineLength) { construction.append(line + "\r\n"); line = indent + " *" + indent(3) + word; } else { line += " " + word; } } construction.append(line); construction.append("\r\n"); } } if (fields.length < 3) { throw new Error("qualifiersTypeAndName should consist of at least three fields separated by |; got " + qualifiersTypeAndName); } if (!"void".equals(fields[1]) && !constructor) { construction.append(indent + " * @return "); construction.append(escapeHTML(fields[1])); if (4 == fields.length) { construction.append("; " + escapeHTML(fields[3])); } construction.append("\r\n"); } if (null != exceptions) { String[] exceptionFields = exceptions.split("[|]"); if (exceptionFields.length != 2) { throw new Error("exceptions should consist of two fields separated by |; got \"" + exceptions + "\""); } construction.append(indent + " * @throws " + exceptionFields[0] + " " + exceptionFields[1] + "\r\n"); } construction.append(indent + " */\r\n"); } else { construction.append(indent + "/** {@inheritDoc} */\r\n" + indent + "@Override\r\n"); } if (null != pragma) { if (pragma.length() == 0) { throw new Error("pragma should not be the empty string"); } construction.append(indent + pragma + "\r\n"); } String line = indent; if (fields[0].length() > 0) { line = line + fields[0] + " "; } if (fields[1].length() > 0) { line += fields[1] + " "; } line += fields[2] + "("; String sep = ""; if (null != parameters) { for (String param : parameters) { if (null == param) { continue; } String[] paramFields = param.split("[|]"); if (!paramFields[1].startsWith("<")) { String paramText = paramFields[0] + " " + paramFields[1]; if (line.length() + sep.length() + paramText.length() + 1 > maxLineLength) { if (!sep.equals("")) { sep = ","; } construction.append(line + sep + "\r\n"); line = indent + indent(2) + paramText; } else { line += sep + paramText; } sep = ", "; } } } line += ")"; construction.append(line); if (null != exceptions) { String append = "throws " + exceptions.split("[|]")[0]; if (line.length() + append.length() > maxLineLength) { construction.append("\r\n" + indent + indent(2) + append); } else { construction.append(" " + append); } } if (null != body) { construction.append("\r\n" + indent + "{\r\n"); final String bodyIndent = indent + indent(1); for (String bodyLine : body) { if (null == bodyLine) { continue; } construction.append(bodyIndent); construction.append(bodyLine); construction.append("\r\n"); } construction.append(indent + "}\r\n"); } else { construction.append(";\r\n"); } construction.append("\r\n"); return construction.toString(); } /** * Replace HTML-special character by their escaped versions. * @param input String; text to convert to clean HTML * @return String; text with correct HTML escapes */ private String escapeHTML(final String input) { StringBuilder construction = new StringBuilder(); for (int pos = 0; pos < input.length(); pos++) { String letter = input.substring(pos, pos + 1); if (letter.equals("&")) { construction.append("&"); } if (letter.equals("<")) { construction.append("<"); } else if (letter.equals(">")) { construction.append(">"); } else if (letter.equals("&")) { construction.append("&"); } else { construction.append(letter); } } return construction.toString(); } /** * Write an interface file that defines an interface that does nothing but define it's own name. * @param relativePackage String; the last element(s) of the package name * @param name String; the name of the class file to write * @param imports String[]; the imports for the new class file * @param description String; the description that is inserted in the block comment at the start of the file * @param genericParams String[]; the descriptions of the generic parameters of the class * @param typeInfo String; the text that goes immediately after the class name * @param body String; the body of the interface file */ public final void generateInterface(final String relativePackage, final String name, final String[] imports, final String description, final String[] genericParams, final String typeInfo, final String body) { try { BufferedWriter writer = openFile(relativePackage, name, imports, description, genericParams); writer.write("public interface " + name + (typeInfo.length() > 0 && typeInfo.startsWith("<") ? "" : " ") + typeInfo + "\r\n{\r\n"); if (null != body) { writer.write(body); } else { writer.write(" // This interface does not force anything to be implemented in classes that " + "implement it\r\n"); } writer.write("}\r\n"); closeFile(writer); } catch (Exception e) { e.printStackTrace(); } } /** * Generate a package-info.java file. * @param relativePackageName String; relative package name * @param contents String; contents of the package-info file */ public final void generatePackageInfo(final String relativePackageName, final String contents) { closeFile(openFile(relativePackageName, packageInfoName, null, contents, null)); } /** * Build a string with the specified number of [] (square bracket) pairs. * @param dimensions int; the number of bracket pairs to concatenate * @return String */ public String buildEmptyBrackets(final int dimensions) { return buildBrackets(dimensions, ""); } /** * Build a string with the specified number of [string] (square bracket with content) pairs. * @param dimensions int; the number of bracket pairs with contents to concatenate * @param contents String; the text that goes between each pair of brackets * @return String */ public String buildBrackets(final int dimensions, final String contents) { String result = ""; for (int i = 0; i < dimensions; i++) { result += "[" + contents + "]"; } return result; } /** * Convert an ArrayList<String> into an array of String. * @param code ArrayList<String>; the lines to convert to an array of string * @return String[]; array containing the strings from the ArrayList */ public static String[] arrayListToArray(final ArrayList code) { String[] codeLines = new String[code.size()]; for (int line = 0; line < code.size(); line++) { codeLines[line] = code.get(line); } return codeLines; } }