View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.view.facelets.el;
20  
21  import java.io.IOException;
22  import java.io.Writer;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.el.ELContext;
27  import javax.el.ELException;
28  import javax.el.ExpressionFactory;
29  import javax.el.ValueExpression;
30  import javax.faces.component.UIComponent;
31  import javax.faces.context.ResponseWriter;
32  import javax.faces.view.Location;
33  
34  import org.apache.myfaces.util.ExternalSpecifications;
35  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
36  
37  /**
38   * Handles parsing EL Strings in accordance with the EL-API Specification. The parser accepts either <code>${..}</code>
39   * or <code>#{..}</code>.
40   * 
41   * @author Jacob Hookom
42   * @version $Id: ELText.java 1187700 2011-10-22 12:19:37Z bommel $
43   */
44  public class ELText
45  {
46  
47      private static final class LiteralValueExpression extends ValueExpression
48      {
49  
50          /**
51           * 
52           */
53          private static final long serialVersionUID = 1L;
54  
55          private final String text;
56  
57          public LiteralValueExpression(String text)
58          {
59              this.text = text;
60          }
61  
62          public boolean isLiteralText()
63          {
64              return false;
65          }
66  
67          public int hashCode()
68          {
69              return 0;
70          }
71  
72          public String getExpressionString()
73          {
74              return this.text;
75          }
76  
77          public boolean equals(Object obj)
78          {
79              return false;
80          }
81  
82          public void setValue(ELContext context, Object value)
83          {
84          }
85  
86          public boolean isReadOnly(ELContext context)
87          {
88              return false;
89          }
90  
91          public Object getValue(ELContext context)
92          {
93              return null;
94          }
95  
96          public Class<?> getType(ELContext context)
97          {
98              return null;
99          }
100 
101         public Class<?> getExpectedType()
102         {
103             return null;
104         }
105 
106     }
107 
108     private static final class ELTextComposite extends ELText
109     {
110         private final ELText[] txt;
111 
112         public ELTextComposite(ELText[] txt)
113         {
114             super(null);
115             this.txt = txt;
116         }
117 
118         public void write(Writer out, ELContext ctx) throws ELException, IOException
119         {
120             for (int i = 0; i < this.txt.length; i++)
121             {
122                 this.txt[i].write(out, ctx);
123             }
124         }
125 
126         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
127         {
128             for (int i = 0; i < this.txt.length; i++)
129             {
130                 this.txt[i].writeText(out, ctx);
131             }
132         }
133 
134         public String toString(ELContext ctx)
135         {
136             StringBuffer sb = new StringBuffer();
137             for (int i = 0; i < this.txt.length; i++)
138             {
139                 sb.append(this.txt[i].toString(ctx));
140             }
141             return sb.toString();
142         }
143 
144         /*
145          * public String toString(ELContext ctx) { StringBuffer sb = new StringBuffer(); for (int i = 0; i <
146          * this.txt.length; i++) { sb.append(this.txt[i].toString(ctx)); } return sb.toString(); }
147          */
148 
149         public String toString()
150         {
151             StringBuffer sb = new StringBuffer();
152             for (int i = 0; i < this.txt.length; i++)
153             {
154                 sb.append(this.txt[i].toString());
155             }
156             return sb.toString();
157         }
158 
159         public boolean isLiteral()
160         {
161             return false;
162         }
163 
164         public ELText apply(ExpressionFactory factory, ELContext ctx)
165         {
166             int len = this.txt.length;
167             ELText[] nt = new ELText[len];
168             for (int i = 0; i < len; i++)
169             {
170                 nt[i] = this.txt[i].apply(factory, ctx);
171             }
172             return new ELTextComposite(nt);
173         }
174     }
175 
176     private static final class ELTextVariable extends ELText
177     {
178         private final ValueExpression ve;
179 
180         public ELTextVariable(ValueExpression ve)
181         {
182             super(ve.getExpressionString());
183             this.ve = ve;
184         }
185 
186         public boolean isLiteral()
187         {
188             return false;
189         }
190 
191         public ELText apply(ExpressionFactory factory, ELContext ctx)
192         {
193             return new ELTextVariable(factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class));
194         }
195 
196         public void write(Writer out, ELContext ctx) throws ELException, IOException
197         {
198             Object v = this.ve.getValue(ctx);
199             if (v != null)
200             {
201                 out.write((String) v);
202             }
203         }
204 
205         public String toString(ELContext ctx) throws ELException
206         {
207             Object v = this.ve.getValue(ctx);
208             if (v != null)
209             {
210                 return v.toString();
211             }
212 
213             return null;
214         }
215 
216         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
217         {
218             Object v = this.ve.getValue(ctx);
219             if (v != null)
220             {
221                 out.writeText((String) v, null);
222             }
223         }
224     }
225     
226     private static final class ELCacheableTextVariable extends ELText
227     {
228         private final ValueExpression ve;
229         
230         //Just like TagAttributeImpl
231         private final static int EL_CC = 2;
232         
233         private final int capabilities;
234         
235         private volatile ELTextVariable cached;
236         
237         public ELCacheableTextVariable(ValueExpression ve)
238         {
239             super(ve.getExpressionString());
240             this.ve = ve;
241             boolean compositeComponentExpression = CompositeComponentELUtils.isCompositeComponentExpression(ve.getExpressionString());
242             this.capabilities = (compositeComponentExpression ? EL_CC : 0);
243         }
244 
245         public boolean isLiteral()
246         {
247             return false;
248         }
249 
250         public ELText apply(ExpressionFactory factory, ELContext ctx)
251         {
252             AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
253             
254             if (actx.isAllowCacheELExpressions() && cached != null)
255             {
256                 return cached;
257             }
258             
259             actx.beforeConstructELExpression();
260             try
261             {
262                 ValueExpression valueExpression = factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class);
263               
264                 if ((this.capabilities & EL_CC) != 0)
265                 {
266                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
267                     if (cc != null)
268                     {
269                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
270                         if (location != null)
271                         {
272                             if (ExternalSpecifications.isUnifiedELAvailable())
273                             {
274                                 valueExpression = new LocationValueExpressionUEL(location, valueExpression);
275                             }
276                             else
277                             {
278                                 valueExpression = new LocationValueExpression(location, valueExpression);
279                             }
280                         }
281                     }
282                 }
283                 
284                 ELTextVariable eltv = new ELTextVariable(valueExpression);
285                 
286                 if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
287                 {
288                      cached = eltv;
289                 }
290                 return eltv;
291             }
292             finally
293             {
294                 actx.afterConstructELExpression();
295             }
296         }
297 
298         public void write(Writer out, ELContext ctx) throws ELException, IOException
299         {
300             Object v = this.ve.getValue(ctx);
301             if (v != null)
302             {
303                 out.write((String) v);
304             }
305         }
306 
307         public String toString(ELContext ctx) throws ELException
308         {
309             Object v = this.ve.getValue(ctx);
310             if (v != null)
311             {
312                 return v.toString();
313             }
314 
315             return null;
316         }
317 
318         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
319         {
320             Object v = this.ve.getValue(ctx);
321             if (v != null)
322             {
323                 out.writeText((String) v, null);
324             }
325         }
326     }
327 
328     protected final String literal;
329 
330     public ELText(String literal)
331     {
332         this.literal = literal;
333     }
334 
335     /**
336      * If it's literal text
337      * 
338      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
339      */
340     public boolean isLiteral()
341     {
342         return true;
343     }
344 
345     /**
346      * Return an instance of <code>this</code> that is applicable given the ELContext and ExpressionFactory state.
347      * 
348      * @param factory
349      *            the ExpressionFactory to use
350      * @param ctx
351      *            the ELContext to use
352      * @return an ELText instance
353      */
354     public ELText apply(ExpressionFactory factory, ELContext ctx)
355     {
356         return this;
357     }
358 
359     /**
360      * Allow this instance to write to the passed Writer, given the ELContext state
361      * 
362      * @param out
363      *            Writer to write to
364      * @param ctx
365      *            current ELContext state
366      * @throws ELException
367      * @throws IOException
368      */
369     public void write(Writer out, ELContext ctx) throws ELException, IOException
370     {
371         out.write(this.literal);
372     }
373 
374     public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
375     {
376         out.writeText(this.literal, null);
377     }
378 
379     /**
380      * Evaluates the ELText to a String
381      * 
382      * @param ctx
383      *            current ELContext state
384      * @throws ELException
385      * @return the evaluated String
386      */
387     public String toString(ELContext ctx) throws ELException
388     {
389         return this.literal;
390     }
391 
392     public String toString()
393     {
394         return this.literal;
395     }
396 
397     /**
398      * Parses the passed string to determine if it's literal or not
399      * 
400      * @param in
401      *            input String
402      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
403      */
404     public static boolean isLiteral(String in)
405     {
406         ELText txt = parse(in);
407         return txt == null || txt.isLiteral();
408     }
409 
410     /**
411      * Factory method for creating an unvalidated ELText instance. NOTE: All expressions in the passed String are
412      * treated as {@link org.apache.myfaces.view.facelets.el.LiteralValueExpression LiteralValueExpressions}.
413      * 
414      * @param in
415      *            String to parse
416      * @return ELText instance that knows if the String was literal or not
417      * @throws javax.el.ELException
418      */
419     public static ELText parse(String in) throws ELException
420     {
421         return parse(null, null, in);
422     }
423 
424     /**
425      * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the
426      * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p/> Variables and
427      * properties will not be evaluated.
428      * 
429      * @param fact
430      *            ExpressionFactory to use
431      * @param ctx
432      *            ELContext to validate against
433      * @param in
434      *            String to parse
435      * @return ELText that can be re-applied later
436      * @throws javax.el.ELException
437      */
438     public static ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException
439     {
440         char[] ca = in.toCharArray();
441         int i = 0;
442         char c = 0;
443         int len = ca.length;
444         int end = len - 1;
445         boolean esc = false;
446         int vlen = 0;
447 
448         StringBuffer buff = new StringBuffer(128);
449         List<ELText> text = new ArrayList<ELText>();
450         ELText t = null;
451         ValueExpression ve = null;
452 
453         while (i < len)
454         {
455             c = ca[i];
456             if ('\\' == c)
457             {
458                 esc = !esc;
459                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
460                 {
461                     i++;
462                     continue;
463                 }
464             }
465             else if (!esc && ('$' == c || '#' == c))
466             {
467                 if (i < end)
468                 {
469                     if ('{' == ca[i + 1])
470                     {
471                         if (buff.length() > 0)
472                         {
473                             text.add(new ELText(buff.toString()));
474                             buff.setLength(0);
475                         }
476                         vlen = findVarLength(ca, i);
477                         if (ctx != null && fact != null)
478                         {
479                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
480                             t = new ELCacheableTextVariable(ve);
481                         }
482                         else
483                         {
484                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
485                         }
486                         text.add(t);
487                         i += vlen;
488                         continue;
489                     }
490                 }
491             }
492             esc = false;
493             buff.append(c);
494             i++;
495         }
496 
497         if (buff.length() > 0)
498         {
499             text.add(new ELText(new String(buff.toString())));
500             buff.setLength(0);
501         }
502 
503         if (text.size() == 0)
504         {
505             return null;
506         }
507         else if (text.size() == 1)
508         {
509             return (ELText) text.get(0);
510         }
511         else
512         {
513             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
514             return new ELTextComposite(ta);
515         }
516     }
517 
518     private static int findVarLength(char[] ca, int s) throws ELException
519     {
520         int i = s;
521         int len = ca.length;
522         char c = 0;
523         int str = 0;
524         while (i < len)
525         {
526             c = ca[i];
527             if ('\\' == c && i < len - 1)
528             {
529                 i++;
530             }
531             else if ('\'' == c || '"' == c)
532             {
533                 if (str == c)
534                 {
535                     str = 0;
536                 }
537                 else
538                 {
539                     str = c;
540                 }
541             }
542             else if (str == 0 && ('}' == c))
543             {
544                 return i - s + 1;
545             }
546             i++;
547         }
548         throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));
549     }
550 
551 }