1 package org.apache.velocity.runtime.parser.node;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with this
6 * work for additional information regarding copyright ownership. The ASF
7 * licenses this file to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 * License for the specific language governing permissions and limitations under
17 * the License.
18 */
19
20 import java.io.IOException;
21 import java.io.StringReader;
22 import java.io.StringWriter;
23
24 import org.apache.commons.lang.text.StrBuilder;
25 import org.apache.velocity.context.InternalContextAdapter;
26 import org.apache.velocity.exception.MethodInvocationException;
27 import org.apache.velocity.exception.ParseErrorException;
28 import org.apache.velocity.exception.ResourceNotFoundException;
29 import org.apache.velocity.exception.TemplateInitException;
30 import org.apache.velocity.exception.VelocityException;
31 import org.apache.velocity.runtime.RuntimeConstants;
32 import org.apache.velocity.runtime.log.Log;
33 import org.apache.velocity.runtime.parser.ParseException;
34 import org.apache.velocity.runtime.parser.Parser;
35 import org.apache.velocity.runtime.parser.Token;
36 import org.apache.velocity.runtime.visitor.BaseVisitor;
37
38 /**
39 * ASTStringLiteral support. Will interpolate!
40 *
41 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
42 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
43 * @version $Id: ASTStringLiteral.java 705297 2008-10-16 17:59:24Z nbubna $
44 */
45 public class ASTStringLiteral extends SimpleNode
46 {
47 /* cache the value of the interpolation switch */
48 private boolean interpolate = true;
49
50 private SimpleNode nodeTree = null;
51
52 private String image = "";
53
54 private String interpolateimage = "";
55
56 /** true if the string contains a line comment (##) */
57 private boolean containsLineComment;
58
59 /**
60 * @param id
61 */
62 public ASTStringLiteral(int id)
63 {
64 super(id);
65 }
66
67 /**
68 * @param p
69 * @param id
70 */
71 public ASTStringLiteral(Parser p, int id)
72 {
73 super(p, id);
74 }
75
76 /**
77 * init : we don't have to do much. Init the tree (there shouldn't be one)
78 * and then see if interpolation is turned on.
79 *
80 * @param context
81 * @param data
82 * @return Init result.
83 * @throws TemplateInitException
84 */
85 public Object init(InternalContextAdapter context, Object data)
86 throws TemplateInitException
87 {
88 /*
89 * simple habit... we prollie don't have an AST beneath us
90 */
91
92 super.init(context, data);
93
94 /*
95 * the stringlit is set at template parse time, so we can do this here
96 * for now. if things change and we can somehow create stringlits at
97 * runtime, this must move to the runtime execution path
98 *
99 * so, only if interpolation is turned on AND it starts with a " AND it
100 * has a directive or reference, then we can interpolate. Otherwise,
101 * don't bother.
102 */
103
104 interpolate = rsvc.getBoolean(
105 RuntimeConstants.INTERPOLATE_STRINGLITERALS, true)
106 && getFirstToken().image.startsWith("\"")
107 && ((getFirstToken().image.indexOf('$') != -1) || (getFirstToken().image
108 .indexOf('#') != -1));
109
110 /*
111 * get the contents of the string, minus the '/" at each end
112 */
113
114 image = getFirstToken().image.substring(1, getFirstToken().image
115 .length() - 1);
116 if (getFirstToken().image.startsWith("\""))
117 {
118 image = unescape(image);
119 }
120
121 /**
122 * note. A kludge on a kludge. The first part, Geir calls this the
123 * dreaded <MORE> kludge. Basically, the use of the <MORE> token eats
124 * the last character of an interpolated string. EXCEPT when a line
125 * comment (##) is in the string this isn't an issue.
126 *
127 * So, to solve this we look for a line comment. If it isn't found we
128 * add a space here and remove it later.
129 */
130
131 /**
132 * Note - this should really use a regexp to look for [^\]## but
133 * apparently escaping of line comments isn't working right now anyway.
134 */
135 containsLineComment = (image.indexOf("##") != -1);
136
137 /*
138 * if appropriate, tack a space on the end (dreaded <MORE> kludge)
139 */
140
141 if (!containsLineComment)
142 {
143 interpolateimage = image + " ";
144 }
145 else
146 {
147 interpolateimage = image;
148 }
149
150 if (interpolate)
151 {
152 /*
153 * now parse and init the nodeTree
154 */
155 StringReader br = new StringReader(interpolateimage);
156
157 /*
158 * it's possible to not have an initialization context - or we don't
159 * want to trust the caller - so have a fallback value if so
160 *
161 * Also, do *not* dump the VM namespace for this template
162 */
163
164 String templateName =
165 (context != null) ? context.getCurrentTemplateName() : "StringLiteral";
166 try
167 {
168 nodeTree = rsvc.parse(br, templateName, false);
169 }
170 catch (ParseException e)
171 {
172 String msg = "Failed to parse String literal at "+
173 Log.formatFileString(templateName, getLine(), getColumn());
174 throw new TemplateInitException(msg, e, templateName, getColumn(), getLine());
175 }
176
177 adjTokenLineNums(nodeTree);
178
179 /*
180 * init with context. It won't modify anything
181 */
182
183 nodeTree.init(context, rsvc);
184 }
185
186 return data;
187 }
188
189 /**
190 * Adjust all the line and column numbers that comprise a node so that they
191 * are corrected for the string literals position within the template file.
192 * This is neccessary if an exception is thrown while processing the node so
193 * that the line and column position reported reflects the error position
194 * within the template and not just relative to the error position within
195 * the string literal.
196 */
197 public void adjTokenLineNums(Node node)
198 {
199 Token tok = node.getFirstToken();
200 // Test against null is probably not neccessary, but just being safe
201 while(tok != null && tok != node.getLastToken())
202 {
203 // If tok is on the first line, then the actual column is
204 // offset by the template column.
205
206 if (tok.beginLine == 1)
207 tok.beginColumn += getColumn();
208
209 if (tok.endLine == 1)
210 tok.endColumn += getColumn();
211
212 tok.beginLine += getLine()- 1;
213 tok.endLine += getLine() - 1;
214 tok = tok.next;
215 }
216 }
217
218
219 /**
220 * @since 1.6
221 */
222 public static String unescape(final String string)
223 {
224 int u = string.indexOf("\\u");
225 if (u < 0) return string;
226
227 StrBuilder result = new StrBuilder();
228
229 int lastCopied = 0;
230
231 for (;;)
232 {
233 result.append(string.substring(lastCopied, u));
234
235 /* we don't worry about an exception here,
236 * because the lexer checked that string is correct */
237 char c = (char) Integer.parseInt(string.substring(u + 2, u + 6), 16);
238 result.append(c);
239
240 lastCopied = u + 6;
241
242 u = string.indexOf("\\u", lastCopied);
243 if (u < 0)
244 {
245 result.append(string.substring(lastCopied));
246 return result.toString();
247 }
248 }
249 }
250
251
252 /**
253 * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
254 * java.lang.Object)
255 */
256 public Object jjtAccept(ParserVisitor visitor, Object data)
257 {
258 return visitor.visit(this, data);
259 }
260
261 /**
262 * Check to see if this is an interpolated string.
263 * @return true if this is constant (not an interpolated string)
264 * @since 1.6
265 */
266 public boolean isConstant()
267 {
268 return !interpolate;
269 }
270
271 /**
272 * renders the value of the string literal If the properties allow, and the
273 * string literal contains a $ or a # the literal is rendered against the
274 * context Otherwise, the stringlit is returned.
275 *
276 * @param context
277 * @return result of the rendering.
278 */
279 public Object value(InternalContextAdapter context)
280 {
281 if (interpolate)
282 {
283 try
284 {
285 /*
286 * now render against the real context
287 */
288
289 StringWriter writer = new StringWriter();
290 nodeTree.render(context, writer);
291
292 /*
293 * and return the result as a String
294 */
295
296 String ret = writer.toString();
297
298 /*
299 * if appropriate, remove the space from the end (dreaded <MORE>
300 * kludge part deux)
301 */
302 if (!containsLineComment && ret.length() > 0)
303 {
304 return ret.substring(0, ret.length() - 1);
305 }
306 else
307 {
308 return ret;
309 }
310 }
311
312 /**
313 * pass through application level runtime exceptions
314 */
315 catch (RuntimeException e)
316 {
317 throw e;
318 }
319
320 catch (IOException e)
321 {
322 String msg = "Error in interpolating string literal";
323 log.error(msg, e);
324 throw new VelocityException(msg, e);
325 }
326
327 }
328
329 /*
330 * ok, either not allowed to interpolate, there wasn't a ref or
331 * directive, or we failed, so just output the literal
332 */
333
334 return image;
335 }
336 }