/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.data.TypeConverter; import java.io.PrintWriter; import java.io.StringWriter; /** * Represents a node of a Java expression. * * This class contains static helper methods for common types of expressions, or you can just create * your own subclass. */ public abstract class JavaExpression { /** * Simple type enumeration to allow us to compare the return types of expressions easily and cast * expressions nicely. */ public enum Type { STRING("String") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { expression = expression.cast(DATA); } return call(Type.STRING, "asString", expression); } }, INT("int") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { expression = expression.cast(DATA); } return call(Type.INT, "asInt", expression); } }, BOOLEAN("boolean") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { expression = expression.cast(DATA); } return call(Type.BOOLEAN, "asBoolean", expression); } }, VALUE("Value") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT); } else { return call(Type.VALUE, "asValue", expression); } } }, DATA("Data") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { return callFindVariable(expression, false); } else { throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n" + expression.toString()); } } }, // This is a string that represents the name of a Data path. VAR_NAME("String") { @Override protected JavaExpression cast(JavaExpression expression) { final JavaExpression stringExpr = expression.cast(Type.STRING); return new JavaExpression(Type.VAR_NAME) { public void write(PrintWriter out) { stringExpr.write(out); } }; } }, // This is a special type because we only cast from DataContext, never to it. DATA_CONTEXT("DataContext") { @Override protected JavaExpression cast(JavaExpression expression) { throw new JSilverCompilationException("Cannot cast to 'DataContext' for expression:\n" + expression.toString()); } }, // This is a special type because we only cast from Data, never to it. MACRO("Macro") { @Override protected JavaExpression cast(JavaExpression expression) { throw new JSilverCompilationException("Cannot cast to 'Macro' for expression:\n" + expression.toString()); } }, // Use this type for JavaExpressions that have no type (such as method // calls with no return value). Wraps the input expression with a // JavaExpression of Type VOID. VOID("Void") { @Override protected JavaExpression cast(final JavaExpression expression) { return new JavaExpression(Type.VOID) { @Override public void write(PrintWriter out) { expression.write(out); } }; } }; /** Useful constant for unknown types */ public static final Type UNKNOWN = null; /** * The Java literal representing the type (e.g. "int", "boolean", "Value") */ public final String symbol; /** * Unconditionally casts the given expression to the type. This should only be called after it * has been determined that the destination type is not the same as the expression type. */ protected abstract JavaExpression cast(JavaExpression expression); private Type(String symbol) { this.symbol = symbol; } } private final Type type; /** * Creates a typed expression. Typed expressions allow for greater optimization by avoiding * unnecessary casting operations. * * @param type the Type of the expression. Must be from the enum above and represent a primitive * or a Class name or void. */ public JavaExpression(Type type) { this.type = type; } /** * Cast this expression to the destination type (possibly a no-op) */ public JavaExpression cast(Type destType) { return (type != destType) ? destType.cast(this) : this; } /** * Gets the type of this expression (or {@code null} if unknown). */ public Type getType() { return type; } /** * Implementations use this to output the expression as Java code. */ public abstract void write(PrintWriter out); @Override public String toString() { StringWriter out = new StringWriter(); write(new PrintWriter(out)); return out.toString(); } /** * An untyped method call (e.g. doStuff(x, "y")). */ public static JavaExpression call(final String method, final JavaExpression... params) { return call(null, method, params); } /** * A typed method call (e.g. doStuff(x, "y")). */ public static JavaExpression call(Type type, final String method, final JavaExpression... params) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, method); out.append('('); boolean seenAnyParams = false; for (JavaExpression param : params) { if (seenAnyParams) { out.append(", "); } else { seenAnyParams = true; } param.write(out); } out.append(')'); } }; } /** * An untyped method call on an instance (e.g. thingy.doStuff(x, "y")). We assume it returns VOID * and thus there is no return value. */ public static JavaExpression callOn(final JavaExpression instance, final String method, final JavaExpression... params) { return callOn(Type.VOID, instance, method, params); } /** * A typed method call on an instance (e.g. thingy.doStuff(x, "y")). */ public static JavaExpression callOn(Type type, final JavaExpression instance, final String method, final JavaExpression... params) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { instance.write(out); out.append('.'); call(method, params).write(out); } }; } /** * A Java string (e.g. "hello\nworld"). */ public static JavaExpression string(String value) { return new StringExpression(value); } public static class StringExpression extends JavaExpression { private final String value; public StringExpression(String value) { super(Type.STRING); this.value = value; } public String getValue() { return value; } @Override public void write(PrintWriter out) { // TODO: This is not production ready yet - needs more // thorough escaping mechanism. out.append('"'); char[] chars = value.toCharArray(); for (char c : chars) { switch (c) { // Single quote (') does not need to be escaped as it's in a // double-quoted (") string. case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\t': out.append("\\t"); break; case '\\': out.append("\\\\"); break; case '"': out.append("\\\""); break; case '\b': out.append("\\b"); break; case '\f': out.append("\\f"); break; default: out.append(c); } } out.append('"'); } } /** * A JavaExpression to represent boolean literal values ('true' or 'false'). */ public static class BooleanLiteralExpression extends JavaExpression { private final boolean value; public static final BooleanLiteralExpression FALSE = new BooleanLiteralExpression(false); public static final BooleanLiteralExpression TRUE = new BooleanLiteralExpression(true); private BooleanLiteralExpression(boolean value) { super(Type.BOOLEAN); this.value = value; } public boolean getValue() { return value; } @Override public void write(PrintWriter out) { out.append(String.valueOf(value)); } } /** * An integer. */ public static JavaExpression integer(String value) { // Just parse it to to check that it is valid TypeConverter.parseNumber(value); return literal(Type.INT, value); } /** * An integer. */ public static JavaExpression integer(int value) { return literal(Type.INT, String.valueOf(value)); } /** * A boolean */ public static JavaExpression bool(boolean value) { return literal(Type.BOOLEAN, value ? "true" : "false"); } /** * An untyped symbol (e.g. myVariable). */ public static JavaExpression symbol(final String value) { return new JavaExpression(Type.UNKNOWN) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, value); } }; } /** * A typed symbol (e.g. myVariable). */ public static JavaExpression symbol(Type type, final String value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, value); } }; } public static JavaExpression macro(final String value) { return symbol(Type.MACRO, value); } /** * A typed assignment (e.g. stuff = doSomething()). */ public static JavaExpression assign(Type type, final String name, final JavaExpression value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, name); out.append(" = "); value.write(out); } }; } /** * A typed assignment with declaration (e.g. String stuff = doSomething()). Use this in preference * when declaring variables from typed expressions. */ public static JavaExpression declare(final Type type, final String name, final JavaExpression value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, type.symbol); out.append(' '); assign(type, name, value).write(out); } }; } /** * An infix expression (e.g. (a + b) ). */ public static JavaExpression infix(Type type, final String operator, final JavaExpression left, final JavaExpression right) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append("("); left.write(out); out.append(" ").append(operator).append(" "); right.write(out); out.append(")"); } }; } /** * An prefix expression (e.g. (-a) ). */ public static JavaExpression prefix(Type type, final String operator, final JavaExpression expression) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append("(").append(operator); expression.write(out); out.append(")"); } }; } /** * A three term inline if expression (e.g. (a ? b : c) ). */ public static JavaExpression inlineIf(Type type, final JavaExpression query, final JavaExpression trueExp, final JavaExpression falseExp) { if (query.getType() != Type.BOOLEAN) { throw new IllegalArgumentException("Expect BOOLEAN expression"); } return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append("("); query.write(out); out.append(" ? "); trueExp.write(out); out.append(" : "); falseExp.write(out); out.append(")"); } }; } /** * An increment statement (e.g. a += b). The difference with infix is that this does not wrap the * expression in parentheses as that is not a valid statement. */ public static JavaExpression increment(Type type, final JavaExpression accumulator, final JavaExpression incr) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { accumulator.write(out); out.append(" += "); incr.write(out); } }; } /** * A literal expression (e.g. anything!). This method injects whatever string it is given into the * Java code - use only in cases where there can be no ambiguity about how the string could be * interpreted by the compiler. */ public static JavaExpression literal(Type type, final String value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append(value); } }; } public static JavaExpression callFindVariable(JavaExpression expression, boolean create) { if (expression.getType() != Type.VAR_NAME) { throw new IllegalArgumentException("Expect VAR_NAME expression"); } return callOn(Type.DATA, TemplateTranslator.DATA_CONTEXT, "findVariable", expression, JavaExpression.bool(create)); } }