• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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