1 /* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.clearsilver.jsilver.compiler; 18 19 import com.google.clearsilver.jsilver.data.TypeConverter; 20 21 import java.io.PrintWriter; 22 import java.io.StringWriter; 23 24 /** 25 * Represents a node of a Java expression. 26 * 27 * This class contains static helper methods for common types of expressions, or you can just create 28 * your own subclass. 29 */ 30 public abstract class JavaExpression { 31 32 /** 33 * Simple type enumeration to allow us to compare the return types of expressions easily and cast 34 * expressions nicely. 35 */ 36 public enum Type { 37 STRING("String") { 38 @Override cast(JavaExpression expression)39 protected JavaExpression cast(JavaExpression expression) { 40 if (expression.getType() == VAR_NAME) { 41 expression = expression.cast(DATA); 42 } 43 return call(Type.STRING, "asString", expression); 44 } 45 }, 46 INT("int") { 47 @Override cast(JavaExpression expression)48 protected JavaExpression cast(JavaExpression expression) { 49 if (expression.getType() == VAR_NAME) { 50 expression = expression.cast(DATA); 51 } 52 return call(Type.INT, "asInt", expression); 53 } 54 }, 55 BOOLEAN("boolean") { 56 @Override cast(JavaExpression expression)57 protected JavaExpression cast(JavaExpression expression) { 58 if (expression.getType() == VAR_NAME) { 59 expression = expression.cast(DATA); 60 } 61 return call(Type.BOOLEAN, "asBoolean", expression); 62 } 63 }, 64 VALUE("Value") { 65 @Override cast(JavaExpression expression)66 protected JavaExpression cast(JavaExpression expression) { 67 if (expression.getType() == VAR_NAME) { 68 return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT); 69 } else { 70 return call(Type.VALUE, "asValue", expression); 71 } 72 } 73 }, 74 DATA("Data") { 75 @Override cast(JavaExpression expression)76 protected JavaExpression cast(JavaExpression expression) { 77 if (expression.getType() == VAR_NAME) { 78 return callFindVariable(expression, false); 79 } else { 80 throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n" 81 + expression.toString()); 82 } 83 } 84 }, 85 // This is a string that represents the name of a Data path. 86 VAR_NAME("String") { 87 @Override cast(JavaExpression expression)88 protected JavaExpression cast(JavaExpression expression) { 89 final JavaExpression stringExpr = expression.cast(Type.STRING); 90 return new JavaExpression(Type.VAR_NAME) { 91 public void write(PrintWriter out) { 92 stringExpr.write(out); 93 } 94 }; 95 } 96 }, 97 // This is a special type because we only cast from DataContext, never to it. 98 DATA_CONTEXT("DataContext") { 99 @Override cast(JavaExpression expression)100 protected JavaExpression cast(JavaExpression expression) { 101 throw new JSilverCompilationException("Cannot cast to 'DataContext' for expression:\n" 102 + expression.toString()); 103 } 104 }, 105 // This is a special type because we only cast from Data, never to it. 106 MACRO("Macro") { 107 @Override cast(JavaExpression expression)108 protected JavaExpression cast(JavaExpression expression) { 109 throw new JSilverCompilationException("Cannot cast to 'Macro' for expression:\n" 110 + expression.toString()); 111 } 112 }, 113 // Use this type for JavaExpressions that have no type (such as method 114 // calls with no return value). Wraps the input expression with a 115 // JavaExpression of Type VOID. 116 VOID("Void") { 117 @Override cast(final JavaExpression expression)118 protected JavaExpression cast(final JavaExpression expression) { 119 return new JavaExpression(Type.VOID) { 120 @Override 121 public void write(PrintWriter out) { 122 expression.write(out); 123 } 124 }; 125 } 126 }; 127 128 /** Useful constant for unknown types */ 129 public static final Type UNKNOWN = null; 130 131 /** 132 * The Java literal representing the type (e.g. "int", "boolean", "Value") 133 */ 134 public final String symbol; 135 136 /** 137 * Unconditionally casts the given expression to the type. This should only be called after it 138 * has been determined that the destination type is not the same as the expression type. 139 */ 140 protected abstract JavaExpression cast(JavaExpression expression); 141 142 private Type(String symbol) { 143 this.symbol = symbol; 144 } 145 } 146 147 private final Type type; 148 149 /** 150 * Creates a typed expression. Typed expressions allow for greater optimization by avoiding 151 * unnecessary casting operations. 152 * 153 * @param type the Type of the expression. Must be from the enum above and represent a primitive 154 * or a Class name or void. 155 */ 156 public JavaExpression(Type type) { 157 this.type = type; 158 } 159 160 /** 161 * Cast this expression to the destination type (possibly a no-op) 162 */ 163 public JavaExpression cast(Type destType) { 164 return (type != destType) ? destType.cast(this) : this; 165 } 166 167 /** 168 * Gets the type of this expression (or {@code null} if unknown). 169 */ 170 public Type getType() { 171 return type; 172 } 173 174 /** 175 * Implementations use this to output the expression as Java code. 176 */ 177 public abstract void write(PrintWriter out); 178 179 @Override 180 public String toString() { 181 StringWriter out = new StringWriter(); 182 write(new PrintWriter(out)); 183 return out.toString(); 184 } 185 186 /** 187 * An untyped method call (e.g. doStuff(x, "y")). 188 */ 189 public static JavaExpression call(final String method, final JavaExpression... params) { 190 return call(null, method, params); 191 } 192 193 /** 194 * A typed method call (e.g. doStuff(x, "y")). 195 */ 196 public static JavaExpression call(Type type, final String method, final JavaExpression... params) { 197 return new JavaExpression(type) { 198 @Override 199 public void write(PrintWriter out) { 200 JavaSourceWriter.writeJavaSymbol(out, method); 201 out.append('('); 202 boolean seenAnyParams = false; 203 for (JavaExpression param : params) { 204 if (seenAnyParams) { 205 out.append(", "); 206 } else { 207 seenAnyParams = true; 208 } 209 param.write(out); 210 } 211 out.append(')'); 212 } 213 }; 214 } 215 216 /** 217 * An untyped method call on an instance (e.g. thingy.doStuff(x, "y")). We assume it returns VOID 218 * and thus there is no return value. 219 */ 220 public static JavaExpression callOn(final JavaExpression instance, final String method, 221 final JavaExpression... params) { 222 return callOn(Type.VOID, instance, method, params); 223 } 224 225 /** 226 * A typed method call on an instance (e.g. thingy.doStuff(x, "y")). 227 */ 228 public static JavaExpression callOn(Type type, final JavaExpression instance, 229 final String method, final JavaExpression... params) { 230 return new JavaExpression(type) { 231 @Override 232 public void write(PrintWriter out) { 233 instance.write(out); 234 out.append('.'); 235 call(method, params).write(out); 236 } 237 }; 238 } 239 240 /** 241 * A Java string (e.g. "hello\nworld"). 242 */ 243 public static JavaExpression string(String value) { 244 return new StringExpression(value); 245 } 246 247 public static class StringExpression extends JavaExpression { 248 249 private final String value; 250 251 public StringExpression(String value) { 252 super(Type.STRING); 253 this.value = value; 254 } 255 256 public String getValue() { 257 return value; 258 } 259 260 @Override 261 public void write(PrintWriter out) { 262 // TODO: This is not production ready yet - needs more 263 // thorough escaping mechanism. 264 out.append('"'); 265 char[] chars = value.toCharArray(); 266 for (char c : chars) { 267 switch (c) { 268 // Single quote (') does not need to be escaped as it's in a 269 // double-quoted (") string. 270 case '\n': 271 out.append("\\n"); 272 break; 273 case '\r': 274 out.append("\\r"); 275 break; 276 case '\t': 277 out.append("\\t"); 278 break; 279 case '\\': 280 out.append("\\\\"); 281 break; 282 case '"': 283 out.append("\\\""); 284 break; 285 case '\b': 286 out.append("\\b"); 287 break; 288 case '\f': 289 out.append("\\f"); 290 break; 291 default: 292 out.append(c); 293 } 294 } 295 out.append('"'); 296 } 297 } 298 299 /** 300 * A JavaExpression to represent boolean literal values ('true' or 'false'). 301 */ 302 public static class BooleanLiteralExpression extends JavaExpression { 303 304 private final boolean value; 305 306 public static final BooleanLiteralExpression FALSE = new BooleanLiteralExpression(false); 307 public static final BooleanLiteralExpression TRUE = new BooleanLiteralExpression(true); 308 309 private BooleanLiteralExpression(boolean value) { 310 super(Type.BOOLEAN); 311 this.value = value; 312 } 313 314 public boolean getValue() { 315 return value; 316 } 317 318 @Override 319 public void write(PrintWriter out) { 320 out.append(String.valueOf(value)); 321 } 322 } 323 324 /** 325 * An integer. 326 */ 327 public static JavaExpression integer(String value) { 328 // Just parse it to to check that it is valid 329 TypeConverter.parseNumber(value); 330 return literal(Type.INT, value); 331 } 332 333 /** 334 * An integer. 335 */ 336 public static JavaExpression integer(int value) { 337 return literal(Type.INT, String.valueOf(value)); 338 } 339 340 /** 341 * A boolean 342 */ 343 public static JavaExpression bool(boolean value) { 344 return literal(Type.BOOLEAN, value ? "true" : "false"); 345 } 346 347 /** 348 * An untyped symbol (e.g. myVariable). 349 */ 350 public static JavaExpression symbol(final String value) { 351 return new JavaExpression(Type.UNKNOWN) { 352 @Override 353 public void write(PrintWriter out) { 354 JavaSourceWriter.writeJavaSymbol(out, value); 355 } 356 }; 357 } 358 359 /** 360 * A typed symbol (e.g. myVariable). 361 */ 362 public static JavaExpression symbol(Type type, final String value) { 363 return new JavaExpression(type) { 364 @Override 365 public void write(PrintWriter out) { 366 JavaSourceWriter.writeJavaSymbol(out, value); 367 } 368 }; 369 } 370 371 public static JavaExpression macro(final String value) { 372 return symbol(Type.MACRO, value); 373 } 374 375 /** 376 * A typed assignment (e.g. stuff = doSomething()). 377 */ 378 public static JavaExpression assign(Type type, final String name, final JavaExpression value) { 379 return new JavaExpression(type) { 380 @Override 381 public void write(PrintWriter out) { 382 JavaSourceWriter.writeJavaSymbol(out, name); 383 out.append(" = "); 384 value.write(out); 385 } 386 }; 387 } 388 389 /** 390 * A typed assignment with declaration (e.g. String stuff = doSomething()). Use this in preference 391 * when declaring variables from typed expressions. 392 */ 393 public static JavaExpression declare(final Type type, final String name, 394 final JavaExpression value) { 395 return new JavaExpression(type) { 396 @Override 397 public void write(PrintWriter out) { 398 JavaSourceWriter.writeJavaSymbol(out, type.symbol); 399 out.append(' '); 400 assign(type, name, value).write(out); 401 } 402 }; 403 } 404 405 /** 406 * An infix expression (e.g. (a + b) ). 407 */ 408 public static JavaExpression infix(Type type, final String operator, final JavaExpression left, 409 final JavaExpression right) { 410 return new JavaExpression(type) { 411 @Override 412 public void write(PrintWriter out) { 413 out.append("("); 414 left.write(out); 415 out.append(" ").append(operator).append(" "); 416 right.write(out); 417 out.append(")"); 418 } 419 }; 420 } 421 422 /** 423 * An prefix expression (e.g. (-a) ). 424 */ 425 public static JavaExpression prefix(Type type, final String operator, 426 final JavaExpression expression) { 427 return new JavaExpression(type) { 428 @Override 429 public void write(PrintWriter out) { 430 out.append("(").append(operator); 431 expression.write(out); 432 out.append(")"); 433 } 434 }; 435 } 436 437 /** 438 * A three term inline if expression (e.g. (a ? b : c) ). 439 */ 440 public static JavaExpression inlineIf(Type type, final JavaExpression query, 441 final JavaExpression trueExp, final JavaExpression falseExp) { 442 if (query.getType() != Type.BOOLEAN) { 443 throw new IllegalArgumentException("Expect BOOLEAN expression"); 444 } 445 return new JavaExpression(type) { 446 @Override 447 public void write(PrintWriter out) { 448 out.append("("); 449 query.write(out); 450 out.append(" ? "); 451 trueExp.write(out); 452 out.append(" : "); 453 falseExp.write(out); 454 out.append(")"); 455 } 456 }; 457 } 458 459 /** 460 * An increment statement (e.g. a += b). The difference with infix is that this does not wrap the 461 * expression in parentheses as that is not a valid statement. 462 */ 463 public static JavaExpression increment(Type type, final JavaExpression accumulator, 464 final JavaExpression incr) { 465 return new JavaExpression(type) { 466 @Override 467 public void write(PrintWriter out) { 468 accumulator.write(out); 469 out.append(" += "); 470 incr.write(out); 471 } 472 }; 473 } 474 475 /** 476 * A literal expression (e.g. anything!). This method injects whatever string it is given into the 477 * Java code - use only in cases where there can be no ambiguity about how the string could be 478 * interpreted by the compiler. 479 */ 480 public static JavaExpression literal(Type type, final String value) { 481 return new JavaExpression(type) { 482 @Override 483 public void write(PrintWriter out) { 484 out.append(value); 485 } 486 }; 487 } 488 489 public static JavaExpression callFindVariable(JavaExpression expression, boolean create) { 490 if (expression.getType() != Type.VAR_NAME) { 491 throw new IllegalArgumentException("Expect VAR_NAME expression"); 492 } 493 return callOn(Type.DATA, TemplateTranslator.DATA_CONTEXT, "findVariable", expression, 494 JavaExpression.bool(create)); 495 } 496 } 497