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.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 }