/* * Copyright (C) 2014-2015 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) * Copyright (C) 2015 University of Freiburg * * This file is part of the ULTIMATE ASTBuilder plug-in. * * The ULTIMATE ASTBuilder plug-in is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE ASTBuilder plug-in 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE ASTBuilder plug-in. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE ASTBuilder plug-in, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE ASTBuilder plug-in grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.astbuilder; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Emits classes etc. * * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) */ @SuppressWarnings("squid:S1192") public class Emit { private static final String INDENT_SPACE = " "; private static final String EMPTY_STRING = ""; private static final String OPEN_PARENTHESIS = "("; private static final String CLOSE_PARENTHESIS_SEMICOLON = ");"; private static final String COMMA = ", "; private static final String SEMICOLON = ";"; private static final String STAR = " * "; private static final String BLANK = " "; private static final String BOOLEAN = "boolean"; private static final int LENGTH_OF_TRUE_PLUS_SPACE = 5; private static final int MIN_NAME_LENGTH = 3; private static final int MAX_PARAM_LENGTH = 80; private static final String DEFAULT_ROOT_CONSTRUCTOR_PARAMETER = EMPTY_STRING; private static final List BOOL_PREFIXES = Arrays.asList("is", "has"); protected PrintWriter mWriter; protected Grammar mGrammar; protected Set mEnumTypes; /** * Constructor. */ public Emit() { mEnumTypes = new HashSet<>(); } public void setGrammar(final Grammar grammar) { mGrammar = grammar; } /** * @param str * String. * @return capitalized string */ public static String capitalize(final String str) { return Character.toTitleCase(str.charAt(0)) + str.substring(1); } /** * @param str * String. * @return uncapitalized string */ public static String uncapitalize(final String str) { return Character.toLowerCase(str.charAt(0)) + str.substring(1); } /** * @param str * String. * @return string with spaces */ public static String breakWords(final String str) { final StringBuilder builder = new StringBuilder(); final int len = str.length(); for (int i = 0; i < len; i++) { final char charr = str.charAt(i); if (Character.isUpperCase(charr) || Character.isTitleCase(charr)) { if (i > 0) { builder.append(' '); } builder.append(Character.toLowerCase(charr)); } else { builder.append(charr); } } return builder.toString(); } /** * @param className * Class name. * @param name * name * @param type * type * @return field comment */ public static String buildFieldComment(final String className, final String name, final String type) { if (BOOLEAN.equals(type) && name.length() >= MIN_NAME_LENGTH && name.startsWith("is") && (Character.isUpperCase(name.charAt(2)) || Character.isTitleCase(name.charAt(2)))) { return "True iff this " + breakWords(className) + BLANK + breakWords(name) + "."; } return "The " + breakWords(name) + " of this " + breakWords(className) + "."; } /** * @param name * name. * @param parent * parent * @return class comment */ public static String buildClassComment(final String name, final String parent) { final StringBuilder builder = new StringBuilder("Represents a "); builder.append(breakWords(name)); if (parent != null) { builder.append(" which is a special form of a ").append(Emit.breakWords(parent)); } builder.append('.'); return builder.toString(); } private static String getShortComment(final String comment) { int end = comment.indexOf('.') + 1; if (end == 0) { end = comment.length(); } return comment.substring(0, end); } protected static void formatComment(final PrintWriter writer, final String indent, final String comment) { writer.println(indent + "/**"); String modifiedComment = comment; int nlIndex = modifiedComment.indexOf('\n'); while (nlIndex >= 0) { writer.println(indent + STAR + modifiedComment.substring(0, nlIndex)); modifiedComment = modifiedComment.substring(nlIndex + 1); nlIndex = modifiedComment.indexOf('\n'); } writer.println(indent + STAR + modifiedComment); writer.println(indent + " */"); } /** * Emits classes. * * @throws IOException * if class writing fails */ public void emitClasses() throws IOException { for (final Node n : mGrammar.getNodeTable().values()) { final String name = n.getName(); System.err.println("Building: " + name); mWriter = new PrintWriter(new FileWriter(name + ".java")); emitNode(n); mWriter.close(); mWriter = null; } } /** * @param node * node. */ public void emitPreamble(final Node node) { mWriter.println(); final String pkgName = mGrammar.getPackageName(); if (pkgName.length() > 0) { mWriter.println("package " + pkgName + SEMICOLON); } for (final String importStr : mGrammar.getImports()) { if (!importStr.endsWith(".*")) { boolean found = false; Node ancestor = node; while (!found && ancestor != null) { /* Check if type is used */ final int dotIndex = importStr.lastIndexOf('.'); final String type = importStr.substring(dotIndex + 1); if (ancestor.getUsedTypes().contains(type)) { found = true; } ancestor = ancestor.getParent(); } if (!found) { continue; } } mWriter.println("import " + importStr + SEMICOLON); } mWriter.println(); } public void emitToplevelComment(final Node node) { mWriter.println(String.format("/* %s -- Automatically generated by TreeBuilder */", node.getName())); } /** * @param node * node. */ public void emitClassDeclaration(final Node node) { mWriter.println("public " + (node.isAbstract() ? "abstract " : EMPTY_STRING) + "class " + node.getName() + (node.getParent() != null ? (" extends " + node.getParent().getName()) : EMPTY_STRING) + (node.getInterfaces() != null ? (" implements " + node.getInterfaces()) : EMPTY_STRING) + " {"); } /** * @param node * node. * @param optional * optional flag for adding parameter * @return constructor parameter */ public String getConstructorParam(final Node node, final boolean optional) { if (node == null) { return EMPTY_STRING; } final StringBuilder builder = new StringBuilder(); builder.append(getConstructorParam(node.getParent(), optional)); String comma = EMPTY_STRING; if (builder.length() > 0) { comma = COMMA; } for (final Parameter parameter : node.getParameters()) { if (optional || !parameter.isOptional()) { final String pname = parameter.getName(); builder.append(comma).append(pname); comma = COMMA; } } return builder.toString(); } /** * @param node * node. * @return root constructor parameter */ @SuppressWarnings("static-method") public String getRootConstructorParam(final Node node) { return DEFAULT_ROOT_CONSTRUCTOR_PARAMETER; } protected void fillConstructorParamComment(final Node node, final StringBuilder constructorParams, final StringBuilder constructorComment, final boolean optional) { final Node parent = node.getParent(); if (parent != null) { fillConstructorParamComment(parent, constructorParams, constructorComment, optional); } String comma = EMPTY_STRING; if (constructorParams.length() > 0) { comma = COMMA; } for (final Parameter parameter : node.getParameters()) { if (optional || !parameter.isOptional()) { final String pname = parameter.getName(); final String pcomment = uncapitalize(getShortComment(parameter.getComment())); constructorComment.append("\n@param ").append(pname).append(' ').append(pcomment); constructorParams.append(comma); constructorParams.append(parameter.getType()).append(' ').append(pname); comma = COMMA; } } } /** * @param node * node. * @param optional * optional flag for adding parameter */ public void emitConstructor(final Node node, final boolean optional) { final String name = node.getName(); final String parentParams; if (node.getParent() == null) { parentParams = getRootConstructorParam(node); } else { parentParams = getConstructorParam(node.getParent(), optional); } final StringBuilder constructorParams = new StringBuilder(); final StringBuilder constructorComment = new StringBuilder("The constructor taking initial values."); fillConstructorParamComment(node, constructorParams, constructorComment, optional); formatComment(mWriter, " ", constructorComment.toString()); mWriter.println(" public " + name + OPEN_PARENTHESIS + constructorParams.toString() + ") {"); if (parentParams != null) { mWriter.println(" super(" + parentParams + CLOSE_PARENTHESIS_SEMICOLON); } for (final Parameter parameter : node.getParameters()) { if (optional || !parameter.isOptional()) { final String pname = parameter.getName(); mWriter.println(" this." + pname + " = " + pname + SEMICOLON); } } emitConstructorAfterParamAssign(node, optional); mWriter.println(" }"); mWriter.println(); } /** * @param node * node. * @param optional * optional flag */ public void emitConstructorAfterParamAssign(final Node node, final boolean optional) { // do nothing per default } /** * @param node * node. */ public void emitConstructors(final Node node) { if (node == null) { throw new IllegalArgumentException(); } int numNotWriteableParams = 0; int numNotOptionalParams = 0; int numTotalParams = 0; /* Default constructor is only emitted if all fields are writeable */ /* Optional constructor is only emitted if there are optional fields */ Node ancestor = node; while (ancestor != null) { for (final Parameter p : ancestor.parameters) { numTotalParams++; if (!p.isWriteable()) { numNotWriteableParams++; } if (!p.isOptional()) { numNotOptionalParams++; } } ancestor = ancestor.getParent(); } if (numNotOptionalParams == 0 || numNotWriteableParams == 0) { formatComment(mWriter, " ", "The default constructor."); mWriter.println(" public " + node.getName() + "() {"); mWriter.println(" }"); mWriter.println(); } if (numNotOptionalParams > 0 && numNotOptionalParams < numTotalParams) { emitConstructor(node, false); } if (numTotalParams > 0) { emitConstructor(node, true); } } private void emitArrayToStringCode(final String name, final String type, final String indent, final int level) { final String ivar = "i" + level; final String modifiedType = type.substring(0, type.length() - 2); final String newindent = indent + INDENT_SPACE; String modifiedName = name; mWriter.println(indent + "if (" + modifiedName + " == null) {"); mWriter.println(indent + " sb.append(\"null\");"); mWriter.println(indent + "} else {"); mWriter.println(indent + " sb.append('[');"); mWriter.println( indent + " for(int " + ivar + " = 0; " + ivar + " < " + modifiedName + ".length; " + ivar + "++) {"); mWriter.println(newindent + "if (" + ivar + " > 0) sb.append(',');"); modifiedName += "[" + ivar + "]"; if (modifiedType.endsWith("[]")) { emitArrayToStringCode(modifiedName, modifiedType, newindent, level + 1); } else { mWriter.println(newindent + " sb.append(" + modifiedName + CLOSE_PARENTHESIS_SEMICOLON); } mWriter.println(indent + " }"); mWriter.println(indent + " sb.append(']');"); mWriter.println(indent + "}"); } /** * @param node * node. */ public void emitNodeHook(final Node node) { // can be overridden by subclasses that want to implement a node hook } /** * @param node * node. */ public void emitNode(final Node node) { final String name = node.getName(); final Parameter[] parameters = node.getParameters(); emitToplevelComment(node); emitPreamble(node); formatComment(mWriter, EMPTY_STRING, node.getComment()); emitClassDeclaration(node); final boolean emitParams = parameters != null && parameters.length > 0; if (emitParams) { emitParams1(parameters); } emitConstructors(node); final String toStringComment = "Returns a textual description of this object."; formatComment(mWriter, " ", toStringComment); mWriter.println(" public String toString() {"); if (emitParams) { emitParams2(name, parameters); } else { mWriter.println(" return \"" + name + "\";"); } mWriter.println(" }"); if (emitParams) { emitParams3(parameters); } emitNodeHook(node); mWriter.println("}"); mWriter.close(); } private void emitParams1(final Parameter[] parameters) { /* collect enum types */ for (int i = 0; i < parameters.length; i++) { String ptype = parameters[i].getType(); if (ptype.startsWith("!")) { /* java 1.5 enum types */ int nextComma = ptype.indexOf(',', 1); if (nextComma == -1) { nextComma = ptype.length(); } final String enumName = ptype.substring(1, nextComma); mWriter.println(" public enum " + enumName + " {"); StringBuilder builder = new StringBuilder(); builder.append(INDENT_SPACE); String comma = EMPTY_STRING; ptype = ptype.substring(nextComma); while (ptype.length() > 0) { nextComma = ptype.indexOf(',', 1); if (nextComma == -1) { nextComma = ptype.length(); } builder.append(comma); if (builder.length() + nextComma > MAX_PARAM_LENGTH) { mWriter.println(builder.toString()); builder = new StringBuilder(); builder.append(INDENT_SPACE); } builder.append(ptype.substring(1, nextComma)); comma = COMMA; ptype = ptype.substring(nextComma); } mWriter.println(builder.toString()); mWriter.println(" }"); mWriter.println(); parameters[i].setType(enumName); mEnumTypes.add(enumName); } else if (ptype.startsWith(",")) { int idx = 0; while (ptype.length() > 0) { int nextComma = ptype.indexOf(',', 1); if (nextComma == -1) { nextComma = ptype.length(); } final String enumeration = ptype.substring(1, nextComma); mWriter.println(" public final static int " + enumeration + " = " + idx + SEMICOLON); idx++; ptype = ptype.substring(nextComma); } mWriter.println(); parameters[i].setType("int"); } } for (int i = 0; i < parameters.length; i++) { formatComment(mWriter, " ", parameters[i].getComment()); mWriter.println(" " + parameters[i].getType() + BLANK + parameters[i].getName() + SEMICOLON); mWriter.println(); } } private void emitParams2(final String name, final Parameter[] parameters) { mWriter.println(" StringBuffer sb = new StringBuffer();"); mWriter.println(" sb.append(\"" + name + "\").append('[');"); String comma = EMPTY_STRING; for (int i = 0; i < parameters.length; i++) { final String pname = parameters[i].getName(); final String ptype = parameters[i].getType(); if (ptype.endsWith("[]")) { if (!EMPTY_STRING.equals(comma)) { mWriter.println(" sb" + comma + SEMICOLON); } emitArrayToStringCode(pname, ptype, INDENT_SPACE, 1); } else { mWriter.println(" sb" + comma + ".append(" + pname + CLOSE_PARENTHESIS_SEMICOLON); } comma = ".append(',')"; } mWriter.println(" return sb.append(']').toString();"); } private void emitParams3(final Parameter[] parameters) { for (int i = 0; i < parameters.length; i++) { mWriter.println(); final String pname = parameters[i].getName(); final String ptype = parameters[i].getType(); final String pcomment = parameters[i].getComment(); final String cpname = capitalize(pname); String getName = "get" + cpname; String setName = "set" + cpname; String getComment; String setComment; if (BOOLEAN.equals(ptype)) { int index = 0; for (final String prefix : BOOL_PREFIXES) { if (pname.startsWith(prefix)) { index = prefix.length(); break; } } if (index > 0) { getName = pname; setName = "set" + pname.substring(index); } String nonTrueComment = pcomment; if (nonTrueComment.startsWith("True ")) { nonTrueComment = nonTrueComment.substring(LENGTH_OF_TRUE_PLUS_SPACE); } getComment = "Checks " + uncapitalize(nonTrueComment) + "\n@return " + uncapitalize(getShortComment(pcomment)); setComment = "Sets " + uncapitalize(nonTrueComment) + "\n@param " + pname + BLANK + uncapitalize(getShortComment(pcomment)); } else { getComment = "Gets " + uncapitalize(pcomment) + "\n@return " + uncapitalize(getShortComment(pcomment)); setComment = "Sets " + uncapitalize(pcomment) + "\n@param " + pname + BLANK + uncapitalize(getShortComment(pcomment)); } formatComment(mWriter, " ", getComment); mWriter.println(" public " + ptype + BLANK + getName + "() {"); mWriter.println(" return " + pname + SEMICOLON); mWriter.println(" }"); if (parameters[i].isWriteable()) { mWriter.println(); formatComment(mWriter, " ", setComment); mWriter.println(" public void " + setName + OPEN_PARENTHESIS + ptype + BLANK + pname + ") {"); if (parameters[i].isWriteableOnce) { mWriter.println(" //Writeable only once"); mWriter.println(" if(this." + pname + " != null && " + pname + " != this." + pname + "){"); mWriter.println(" throw new AssertionError(\"Value is only writeable once\");"); mWriter.println(" }"); } mWriter.println(" this." + pname + " = " + pname + SEMICOLON); mWriter.println(" }"); } } } }