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 static com.google.clearsilver.jsilver.compiler.JavaExpression.StringExpression;
20 import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
21 import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal;
22 import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
23 import com.google.clearsilver.jsilver.syntax.node.ADecNumberVariable;
24 import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
25 import com.google.clearsilver.jsilver.syntax.node.AExpandVariable;
26 import com.google.clearsilver.jsilver.syntax.node.AHexNumberVariable;
27 import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
28 import com.google.clearsilver.jsilver.syntax.node.PVariable;
29 
30 import java.io.PrintWriter;
31 import java.io.StringWriter;
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * Translates a variable name (e.g. search.results.3.title) into the Java code for use as a key in
37  * looking up a variable (e.g. "search.results.3.title").
38  *
39  * While it is possible to reuse an instance of this class repeatedly, it is not thread safe or
40  * reentrant. Evaluating an expression such as: <code>a.b[c.d]</code> would require two instances.
41  */
42 public class VariableTranslator extends DepthFirstAdapter {
43 
44   private List<JavaExpression> components;
45 
46   private final ExpressionTranslator expressionTranslator;
47 
VariableTranslator(ExpressionTranslator expressionTranslator)48   public VariableTranslator(ExpressionTranslator expressionTranslator) {
49     this.expressionTranslator = expressionTranslator;
50   }
51 
52   /**
53    * See class description.
54    *
55    * @param csVariable Variable node in template's AST.
56    * @return Appropriate code (as JavaExpression).
57    */
translate(PVariable csVariable)58   public JavaExpression translate(PVariable csVariable) {
59     try {
60       assert components == null;
61       components = new ArrayList<JavaExpression>();
62       csVariable.apply(this);
63       components = joinComponentsWithDots(components);
64       components = combineAdjacentStrings(components);
65       return concatenate(components);
66     } finally {
67       components = null;
68     }
69   }
70 
71   @Override
caseANameVariable(ANameVariable node)72   public void caseANameVariable(ANameVariable node) {
73     components.add(new StringExpression(node.getWord().getText()));
74   }
75 
76   @Override
caseADecNumberVariable(ADecNumberVariable node)77   public void caseADecNumberVariable(ADecNumberVariable node) {
78     components.add(new StringExpression(node.getDecNumber().getText()));
79   }
80 
81   @Override
caseAHexNumberVariable(AHexNumberVariable node)82   public void caseAHexNumberVariable(AHexNumberVariable node) {
83     components.add(new StringExpression(node.getHexNumber().getText()));
84   }
85 
86   @Override
caseADescendVariable(ADescendVariable node)87   public void caseADescendVariable(ADescendVariable node) {
88     node.getParent().apply(this);
89     node.getChild().apply(this);
90   }
91 
92   @Override
caseAExpandVariable(AExpandVariable node)93   public void caseAExpandVariable(AExpandVariable node) {
94     node.getParent().apply(this);
95     components.add(expressionTranslator.translateToString(node.getChild()));
96   }
97 
98   /**
99    * Inserts dots between each component in the path.
100    *
101    * e.g. from: "a", "b", something, "c" to: "a", ".", "b", ".", something, ".", "c"
102    */
joinComponentsWithDots(List<JavaExpression> in)103   private List<JavaExpression> joinComponentsWithDots(List<JavaExpression> in) {
104     List<JavaExpression> out = new ArrayList<JavaExpression>(in.size() * 2);
105     for (JavaExpression component : in) {
106       if (!out.isEmpty()) {
107         out.add(DOT);
108       }
109       out.add(component);
110     }
111     return out;
112   }
113 
114   private static final JavaExpression DOT = new StringExpression(".");
115 
116   /**
117    * Combines adjacent strings.
118    *
119    * e.g. from: "a", ".", "b", ".", something, ".", "c" to : "a.b.", something, ".c"
120    */
combineAdjacentStrings(List<JavaExpression> in)121   private List<JavaExpression> combineAdjacentStrings(List<JavaExpression> in) {
122     assert !in.isEmpty();
123     List<JavaExpression> out = new ArrayList<JavaExpression>(in.size());
124     JavaExpression last = null;
125     for (JavaExpression current : in) {
126       if (last == null) {
127         last = current;
128         continue;
129       }
130       if (current instanceof StringExpression && last instanceof StringExpression) {
131         // Last and current are both strings - combine them.
132         StringExpression currentString = (StringExpression) current;
133         StringExpression lastString = (StringExpression) last;
134         last = new StringExpression(lastString.getValue() + currentString.getValue());
135       } else {
136         out.add(last);
137         last = current;
138       }
139     }
140     out.add(last);
141     return out;
142   }
143 
144   /**
145    * Concatenate a list of JavaExpressions into a single string.
146    *
147    * e.g. from: "a", "b", stuff to : "a" + "b" + stuff
148    */
concatenate(List<JavaExpression> expressions)149   private JavaExpression concatenate(List<JavaExpression> expressions) {
150     StringWriter buffer = new StringWriter();
151     PrintWriter out = new PrintWriter(buffer);
152     boolean seenFirst = false;
153     for (JavaExpression expression : expressions) {
154       if (seenFirst) {
155         out.print(" + ");
156       }
157       seenFirst = true;
158       expression.write(out);
159     }
160     return literal(Type.VAR_NAME, buffer.toString());
161   }
162 
163 }
164