View Javadoc

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 }