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 1351627 2012-06-19 09:50:21Z lu4242 $
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                 // In TagAttributeImpl.getValueExpression(), it is necessary to do an
257                 // special logic to detect the cases where #{cc} is included into the
258                 // EL expression and set the proper ccLevel. In this case, it is usual
259                 // the parent composite component is always on top, but it is possible to
260                 // write a nesting case with <composite:insertChildren>, and
261                 // pass a flat EL expression over itself. So, it is necessary to update
262                 // the ccLevel to make possible to find the right parent where this 
263                 // expression belongs to.
264                 if ((this.capabilities & EL_CC) != 0)
265                 {
266                     return new ELTextVariable(((LocationValueExpression)cached.ve).apply(
267                             actx.getFaceletCompositionContext().getCompositeComponentLevel()));
268                 }
269                 return cached;
270             }
271             
272             actx.beforeConstructELExpression();
273             try
274             {
275                 ValueExpression valueExpression = factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class);
276               
277                 if ((this.capabilities & EL_CC) != 0)
278                 {
279                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
280                     if (cc != null)
281                     {
282                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
283                         if (location != null)
284                         {
285                             if (ExternalSpecifications.isUnifiedELAvailable())
286                             {
287                                 valueExpression = new LocationValueExpressionUEL(location, valueExpression,
288                                         actx.getFaceletCompositionContext().getCompositeComponentLevel());
289                             }
290                             else
291                             {
292                                 valueExpression = new LocationValueExpression(location, valueExpression,
293                                         actx.getFaceletCompositionContext().getCompositeComponentLevel());
294                             }
295                         }
296                     }
297                 }
298                 
299                 ELTextVariable eltv = new ELTextVariable(valueExpression);
300                 
301                 if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
302                 {
303                      cached = eltv;
304                 }
305                 return eltv;
306             }
307             finally
308             {
309                 actx.afterConstructELExpression();
310             }
311         }
312 
313         public void write(Writer out, ELContext ctx) throws ELException, IOException
314         {
315             Object v = this.ve.getValue(ctx);
316             if (v != null)
317             {
318                 out.write((String) v);
319             }
320         }
321 
322         public String toString(ELContext ctx) throws ELException
323         {
324             Object v = this.ve.getValue(ctx);
325             if (v != null)
326             {
327                 return v.toString();
328             }
329 
330             return null;
331         }
332 
333         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
334         {
335             Object v = this.ve.getValue(ctx);
336             if (v != null)
337             {
338                 out.writeText((String) v, null);
339             }
340         }
341     }
342 
343     protected final String literal;
344 
345     public ELText(String literal)
346     {
347         this.literal = literal;
348     }
349 
350     /**
351      * If it's literal text
352      * 
353      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
354      */
355     public boolean isLiteral()
356     {
357         return true;
358     }
359 
360     /**
361      * Return an instance of <code>this</code> that is applicable given the ELContext and ExpressionFactory state.
362      * 
363      * @param factory
364      *            the ExpressionFactory to use
365      * @param ctx
366      *            the ELContext to use
367      * @return an ELText instance
368      */
369     public ELText apply(ExpressionFactory factory, ELContext ctx)
370     {
371         return this;
372     }
373 
374     /**
375      * Allow this instance to write to the passed Writer, given the ELContext state
376      * 
377      * @param out
378      *            Writer to write to
379      * @param ctx
380      *            current ELContext state
381      * @throws ELException
382      * @throws IOException
383      */
384     public void write(Writer out, ELContext ctx) throws ELException, IOException
385     {
386         out.write(this.literal);
387     }
388 
389     public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
390     {
391         out.writeText(this.literal, null);
392     }
393 
394     /**
395      * Evaluates the ELText to a String
396      * 
397      * @param ctx
398      *            current ELContext state
399      * @throws ELException
400      * @return the evaluated String
401      */
402     public String toString(ELContext ctx) throws ELException
403     {
404         return this.literal;
405     }
406 
407     public String toString()
408     {
409         return this.literal;
410     }
411 
412     /**
413      * Parses the passed string to determine if it's literal or not
414      * 
415      * @param in
416      *            input String
417      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
418      */
419     public static boolean isLiteral(String in)
420     {
421         ELText txt = parse(in);
422         return txt == null || txt.isLiteral();
423     }
424 
425     /**
426      * Factory method for creating an unvalidated ELText instance. NOTE: All expressions in the passed String are
427      * treated as {@link org.apache.myfaces.view.facelets.el.LiteralValueExpression LiteralValueExpressions}.
428      * 
429      * @param in
430      *            String to parse
431      * @return ELText instance that knows if the String was literal or not
432      * @throws javax.el.ELException
433      */
434     public static ELText parse(String in) throws ELException
435     {
436         return parse(null, null, in);
437     }
438 
439     /**
440      * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the
441      * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p/> Variables and
442      * properties will not be evaluated.
443      * 
444      * @param fact
445      *            ExpressionFactory to use
446      * @param ctx
447      *            ELContext to validate against
448      * @param in
449      *            String to parse
450      * @return ELText that can be re-applied later
451      * @throws javax.el.ELException
452      */
453     public static ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException
454     {
455         char[] ca = in.toCharArray();
456         int i = 0;
457         char c = 0;
458         int len = ca.length;
459         int end = len - 1;
460         boolean esc = false;
461         int vlen = 0;
462 
463         StringBuffer buff = new StringBuffer(128);
464         List<ELText> text = new ArrayList<ELText>();
465         ELText t = null;
466         ValueExpression ve = null;
467 
468         while (i < len)
469         {
470             c = ca[i];
471             if ('\\' == c)
472             {
473                 esc = !esc;
474                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
475                 {
476                     i++;
477                     continue;
478                 }
479             }
480             else if (!esc && ('$' == c || '#' == c))
481             {
482                 if (i < end)
483                 {
484                     if ('{' == ca[i + 1])
485                     {
486                         if (buff.length() > 0)
487                         {
488                             text.add(new ELText(buff.toString()));
489                             buff.setLength(0);
490                         }
491                         vlen = findVarLength(ca, i);
492                         if (ctx != null && fact != null)
493                         {
494                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
495                             t = new ELCacheableTextVariable(ve);
496                         }
497                         else
498                         {
499                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
500                         }
501                         text.add(t);
502                         i += vlen;
503                         continue;
504                     }
505                 }
506             }
507             esc = false;
508             buff.append(c);
509             i++;
510         }
511 
512         if (buff.length() > 0)
513         {
514             text.add(new ELText(new String(buff.toString())));
515             buff.setLength(0);
516         }
517 
518         if (text.size() == 0)
519         {
520             return null;
521         }
522         else if (text.size() == 1)
523         {
524             return (ELText) text.get(0);
525         }
526         else
527         {
528             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
529             return new ELTextComposite(ta);
530         }
531     }
532 
533     private static int findVarLength(char[] ca, int s) throws ELException
534     {
535         int i = s;
536         int len = ca.length;
537         char c = 0;
538         int str = 0;
539         while (i < len)
540         {
541             c = ca[i];
542             if ('\\' == c && i < len - 1)
543             {
544                 i++;
545             }
546             else if ('\'' == c || '"' == c)
547             {
548                 if (str == c)
549                 {
550                     str = 0;
551                 }
552                 else
553                 {
554                     str = c;
555                 }
556             }
557             else if (str == 0 && ('}' == c))
558             {
559                 return i - s + 1;
560             }
561             i++;
562         }
563         throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));
564     }
565 
566 }