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.BufferedReader;
21 import java.io.IOException;
22 import java.io.StringReader;
23 import java.io.StringWriter;
24
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.runtime.RuntimeConstants;
31 import org.apache.velocity.runtime.parser.ParseException;
32 import org.apache.velocity.runtime.parser.Parser;
33
34 /**
35 * ASTStringLiteral support. Will interpolate!
36 *
37 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
38 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
39 * @version $Id: ASTStringLiteral.java 525317 2007-04-03 22:50:22Z nbubna $
40 */
41 public class ASTStringLiteral extends SimpleNode
42 {
43 /* cache the value of the interpolation switch */
44 private boolean interpolate = true;
45
46 private SimpleNode nodeTree = null;
47
48 private String image = "";
49
50 private String interpolateimage = "";
51
52 /** true if the string contains a line comment (##) */
53 private boolean containsLineComment;
54
55 /**
56 * @param id
57 */
58 public ASTStringLiteral(int id)
59 {
60 super(id);
61 }
62
63 /**
64 * @param p
65 * @param id
66 */
67 public ASTStringLiteral(Parser p, int id)
68 {
69 super(p, id);
70 }
71
72 /**
73 * init : we don't have to do much. Init the tree (there shouldn't be one)
74 * and then see if interpolation is turned on.
75 *
76 * @param context
77 * @param data
78 * @return Init result.
79 * @throws TemplateInitException
80 */
81 public Object init(InternalContextAdapter context, Object data)
82 throws TemplateInitException
83 {
84 /*
85 * simple habit... we prollie don't have an AST beneath us
86 */
87
88 super.init(context, data);
89
90 /*
91 * the stringlit is set at template parse time, so we can do this here
92 * for now. if things change and we can somehow create stringlits at
93 * runtime, this must move to the runtime execution path
94 *
95 * so, only if interpolation is turned on AND it starts with a " AND it
96 * has a directive or reference, then we can interpolate. Otherwise,
97 * don't bother.
98 */
99
100 interpolate = rsvc.getBoolean(
101 RuntimeConstants.INTERPOLATE_STRINGLITERALS, true)
102 && getFirstToken().image.startsWith("\"")
103 && ((getFirstToken().image.indexOf('$') != -1) || (getFirstToken().image
104 .indexOf('#') != -1));
105
106 /*
107 * get the contents of the string, minus the '/" at each end
108 */
109
110 image = getFirstToken().image.substring(1, getFirstToken().image
111 .length() - 1);
112 if (getFirstToken().image.startsWith("\""))
113 {
114 image = unescape(image);
115 }
116
117 /**
118 * note. A kludge on a kludge. The first part, Geir calls this the
119 * dreaded <MORE> kludge. Basically, the use of the <MORE> token eats
120 * the last character of an interpolated string. EXCEPT when a line
121 * comment (##) is in the string this isn't an issue.
122 *
123 * So, to solve this we look for a line comment. If it isn't found we
124 * add a space here and remove it later.
125 */
126
127 /**
128 * Note - this should really use a regexp to look for [^\]## but
129 * apparently escaping of line comments isn't working right now anyway.
130 */
131 containsLineComment = (image.indexOf("##") != -1);
132
133 /*
134 * if appropriate, tack a space on the end (dreaded <MORE> kludge)
135 */
136
137 if (!containsLineComment)
138 {
139 interpolateimage = image + " ";
140 }
141 else
142 {
143 interpolateimage = image;
144 }
145
146 if (interpolate)
147 {
148 /*
149 * now parse and init the nodeTree
150 */
151 BufferedReader br = new BufferedReader(new StringReader(
152 interpolateimage));
153
154 /*
155 * it's possible to not have an initialization context - or we don't
156 * want to trust the caller - so have a fallback value if so
157 *
158 * Also, do *not* dump the VM namespace for this template
159 */
160
161 try
162 {
163 nodeTree = rsvc.parse(br, (context != null) ? context
164 .getCurrentTemplateName() : "StringLiteral", false);
165 }
166 catch (ParseException e)
167 {
168 throw new TemplateInitException(
169 "Problem parsing String literal.", e,
170 (context != null) ? context.getCurrentTemplateName()
171 : "StringLiteral", getColumn(), getLine());
172 }
173
174 /*
175 * init with context. It won't modify anything
176 */
177
178 nodeTree.init(context, rsvc);
179 }
180
181 return data;
182 }
183
184 public static String unescape(final String string)
185 {
186 int u = string.indexOf("\\u");
187 if (u < 0) return string;
188
189 StringBuffer result = new StringBuffer();
190
191 int lastCopied = 0;
192
193 for (;;)
194 {
195 result.append(string.substring(lastCopied, u));
196
197 /* we don't worry about an exception here,
198 * because the lexer checked that string is correct */
199 char c = (char) Integer.parseInt(string.substring(u + 2, u + 6), 16);
200 result.append(c);
201
202 lastCopied = u + 6;
203
204 u = string.indexOf("\\u", lastCopied);
205 if (u < 0)
206 {
207 result.append(string.substring(lastCopied));
208 return result.toString();
209 }
210 }
211 }
212
213
214 /**
215 * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
216 * java.lang.Object)
217 */
218 public Object jjtAccept(ParserVisitor visitor, Object data)
219 {
220 return visitor.visit(this, data);
221 }
222
223 /**
224 * renders the value of the string literal If the properties allow, and the
225 * string literal contains a $ or a # the literal is rendered against the
226 * context Otherwise, the stringlit is returned.
227 *
228 * @param context
229 * @return result of the rendering.
230 */
231 public Object value(InternalContextAdapter context)
232 {
233 if (interpolate)
234 {
235 try
236 {
237 /*
238 * now render against the real context
239 */
240
241 StringWriter writer = new StringWriter();
242 nodeTree.render(context, writer);
243
244 /*
245 * and return the result as a String
246 */
247
248 String ret = writer.toString();
249
250 /*
251 * if appropriate, remove the space from the end (dreaded <MORE>
252 * kludge part deux)
253 */
254 if (!containsLineComment && ret.length() > 0)
255 {
256 return ret.substring(0, ret.length() - 1);
257 }
258 else
259 {
260 return ret;
261 }
262 }
263
264 /**
265 * For interpolated Strings we do not pass exceptions through --
266 * just log the problem and move on.
267 */
268 catch (ParseErrorException e)
269 {
270 log.error("Error in interpolating string literal", e);
271 }
272 catch (MethodInvocationException e)
273 {
274 log.error("Error in interpolating string literal", e);
275 }
276 catch (ResourceNotFoundException e)
277 {
278 log.error("Error in interpolating string literal", e);
279 }
280
281 /**
282 * pass through application level runtime exceptions
283 */
284 catch (RuntimeException e)
285 {
286 throw e;
287 }
288
289 catch (IOException e)
290 {
291 log.error("Error in interpolating string literal", e);
292 }
293
294 }
295
296 /*
297 * ok, either not allowed to interpolate, there wasn't a ref or
298 * directive, or we failed, so just output the literal
299 */
300
301 return image;
302 }
303 }