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
17package com.google.clearsilver.jsilver.compiler;
18
19import static com.google.clearsilver.jsilver.compiler.JavaExpression.StringExpression;
20import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
21import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal;
22import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
23import com.google.clearsilver.jsilver.syntax.node.ADecNumberVariable;
24import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
25import com.google.clearsilver.jsilver.syntax.node.AExpandVariable;
26import com.google.clearsilver.jsilver.syntax.node.AHexNumberVariable;
27import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
28import com.google.clearsilver.jsilver.syntax.node.PVariable;
29
30import java.io.PrintWriter;
31import java.io.StringWriter;
32import java.util.ArrayList;
33import 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 */
42public class VariableTranslator extends DepthFirstAdapter {
43
44  private List<JavaExpression> components;
45
46  private final ExpressionTranslator expressionTranslator;
47
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   */
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
72  public void caseANameVariable(ANameVariable node) {
73    components.add(new StringExpression(node.getWord().getText()));
74  }
75
76  @Override
77  public void caseADecNumberVariable(ADecNumberVariable node) {
78    components.add(new StringExpression(node.getDecNumber().getText()));
79  }
80
81  @Override
82  public void caseAHexNumberVariable(AHexNumberVariable node) {
83    components.add(new StringExpression(node.getHexNumber().getText()));
84  }
85
86  @Override
87  public void caseADescendVariable(ADescendVariable node) {
88    node.getParent().apply(this);
89    node.getChild().apply(this);
90  }
91
92  @Override
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   */
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   */
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   */
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