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