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.view.facelets.AbstractFaceletContext;
35  
36  /**
37   * Handles parsing EL Strings in accordance with the EL-API Specification.
38   * The parser accepts either <code>${..}</code>
39   * or <code>#{..}</code>.
40   * 
41   * @author Jacob Hookom
42   * @version $Id$
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 static int EL_RESOURCE = 8;
234         
235         private final int capabilities;
236         
237         private volatile ELTextVariable cached;
238         
239         public ELCacheableTextVariable(ValueExpression ve)
240         {
241             super(ve.getExpressionString());
242             this.ve = ve;
243             boolean compositeComponentExpression
244                     = CompositeComponentELUtils.isCompositeComponentExpression(ve.getExpressionString());
245             boolean resourceExpression = ResourceELUtils.isResourceExpression(ve.getExpressionString());
246             this.capabilities = (compositeComponentExpression ? EL_CC : 0) | ( resourceExpression ? EL_RESOURCE : 0);
247         }
248 
249         public boolean isLiteral()
250         {
251             return false;
252         }
253 
254         public ELText apply(ExpressionFactory factory, ELContext ctx)
255         {
256             AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
257             
258             if (actx.isAllowCacheELExpressions() && cached != null)
259             {
260                 // In TagAttributeImpl.getValueExpression(), it is necessary to do an
261                 // special logic to detect the cases where #{cc} is included into the
262                 // EL expression and set the proper ccLevel. In this case, it is usual
263                 // the parent composite component is always on top, but it is possible to
264                 // write a nesting case with <composite:insertChildren>, and
265                 // pass a flat EL expression over itself. So, it is necessary to update
266                 // the ccLevel to make possible to find the right parent where this 
267                 // expression belongs to.
268                 if ((this.capabilities & EL_CC) != 0)
269                 {
270                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
271                     if (cc != null)
272                     {
273                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
274                         if (location != null)
275                         {
276                             return new ELTextVariable(((LocationValueExpression)cached.ve).apply(
277                                     actx.getFaceletCompositionContext().getCompositeComponentLevel(), location));
278                         }
279                     }
280                     return new ELTextVariable(((LocationValueExpression)cached.ve).apply(
281                             actx.getFaceletCompositionContext().getCompositeComponentLevel()));
282                 }
283                 return cached;
284             }
285             
286             actx.beforeConstructELExpression();
287             try
288             {
289                 ValueExpression valueExpression
290                         = factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class);
291               
292                 if ((this.capabilities & EL_CC) != 0)
293                 {
294                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
295                     if (cc != null)
296                     {
297                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
298                         if (location != null)
299                         {
300                             valueExpression = new LocationValueExpression(location, valueExpression,
301                                     actx.getFaceletCompositionContext().getCompositeComponentLevel());
302                         }
303                     }
304                 }
305                 else if ((this.capabilities & EL_RESOURCE) != 0)
306                 {
307                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
308                     if (cc != null)
309                     {
310                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
311                         if (location != null)
312                         {
313                             valueExpression = new ResourceLocationValueExpression(location, valueExpression);
314                         }
315                     }
316                 }
317                 
318                 ELTextVariable eltv = new ELTextVariable(valueExpression);
319                 
320                 if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
321                 {
322                      cached = eltv;
323                 }
324                 return eltv;
325             }
326             finally
327             {
328                 actx.afterConstructELExpression();
329             }
330         }
331 
332         public void write(Writer out, ELContext ctx) throws ELException, IOException
333         {
334             Object v = this.ve.getValue(ctx);
335             if (v != null)
336             {
337                 out.write((String) v);
338             }
339         }
340 
341         public String toString(ELContext ctx) throws ELException
342         {
343             Object v = this.ve.getValue(ctx);
344             if (v != null)
345             {
346                 return v.toString();
347             }
348 
349             return null;
350         }
351 
352         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
353         {
354             Object v = this.ve.getValue(ctx);
355             if (v != null)
356             {
357                 out.writeText((String) v, null);
358             }
359         }
360     }
361 
362     protected final String literal;
363 
364     public ELText(String literal)
365     {
366         this.literal = literal;
367     }
368 
369     /**
370      * If it's literal text
371      * 
372      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
373      */
374     public boolean isLiteral()
375     {
376         return true;
377     }
378 
379     /**
380      * Return an instance of <code>this</code> that is applicable given the ELContext and ExpressionFactory state.
381      * 
382      * @param factory
383      *            the ExpressionFactory to use
384      * @param ctx
385      *            the ELContext to use
386      * @return an ELText instance
387      */
388     public ELText apply(ExpressionFactory factory, ELContext ctx)
389     {
390         return this;
391     }
392 
393     /**
394      * Allow this instance to write to the passed Writer, given the ELContext state
395      * 
396      * @param out
397      *            Writer to write to
398      * @param ctx
399      *            current ELContext state
400      * @throws ELException
401      * @throws IOException
402      */
403     public void write(Writer out, ELContext ctx) throws ELException, IOException
404     {
405         out.write(this.literal);
406     }
407 
408     public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
409     {
410         out.writeText(this.literal, null);
411     }
412 
413     /**
414      * Evaluates the ELText to a String
415      * 
416      * @param ctx
417      *            current ELContext state
418      * @throws ELException
419      * @return the evaluated String
420      */
421     public String toString(ELContext ctx) throws ELException
422     {
423         return this.literal;
424     }
425 
426     public String toString()
427     {
428         return this.literal;
429     }
430 
431     /**
432      * Parses the passed string to determine if it's literal or not
433      * 
434      * @param in
435      *            input String
436      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
437      */
438     public static boolean isLiteral(String in)
439     {
440         //ELText txt = parse(in);
441         //return txt == null || txt.isLiteral();
442         return isLiteral(null, null, in);
443     }
444 
445     /**
446      * Factory method for creating an unvalidated ELText instance. NOTE: All expressions in the passed String are
447      * treated as {@link org.apache.myfaces.view.facelets.el.LocationValueExpression}.
448      * 
449      * @param in
450      *            String to parse
451      * @return ELText instance that knows if the String was literal or not
452      * @throws javax.el.ELException
453      */
454     public static ELText parse(String in) throws ELException
455     {
456         return parse(null, null, in);
457     }
458     
459     public static ELText parseAllowEmptyString(String in) throws ELException
460     {
461         if (in != null && in.length() == 0)
462         {
463             return new ELText(in);
464         }
465         else
466         {
467             return parse(null, null, in);
468         }
469     }
470 
471     /**
472      * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the
473      * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p> Variables and
474      * properties will not be evaluated.</p>
475      * 
476      * @param fact
477      *            ExpressionFactory to use
478      * @param ctx
479      *            ELContext to validate against
480      * @param in
481      *            String to parse
482      * @return ELText that can be re-applied later
483      * @throws javax.el.ELException
484      */
485     public static ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException
486     {
487         char[] ca = in.toCharArray();
488         int i = 0;
489         char c = 0;
490         int len = ca.length;
491         int end = len - 1;
492         boolean esc = false;
493         int vlen = 0;
494 
495         StringBuffer buff = new StringBuffer(128);
496         List<ELText> text = new ArrayList<ELText>();
497         ELText t = null;
498         ValueExpression ve = null;
499 
500         while (i < len)
501         {
502             c = ca[i];
503             if ('\\' == c)
504             {
505                 esc = !esc;
506                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
507                 {
508                     i++;
509                     continue;
510                 }
511             }
512             else if (!esc && ('$' == c || '#' == c))
513             {
514                 if (i < end)
515                 {
516                     if ('{' == ca[i + 1])
517                     {
518                         if (buff.length() > 0)
519                         {
520                             text.add(new ELText(buff.toString()));
521                             buff.setLength(0);
522                         }
523                         vlen = findVarLength(ca, i);
524                         if (ctx != null && fact != null)
525                         {
526                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
527                             t = new ELCacheableTextVariable(ve);
528                         }
529                         else
530                         {
531                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
532                         }
533                         text.add(t);
534                         i += vlen;
535                         continue;
536                     }
537                 }
538             }
539             esc = false;
540             buff.append(c);
541             i++;
542         }
543 
544         if (buff.length() > 0)
545         {
546             text.add(new ELText(buff.toString()));
547             buff.setLength(0);
548         }
549 
550         if (text.isEmpty())
551         {
552             return null;
553         }
554         else if (text.size() == 1)
555         {
556             return (ELText) text.get(0);
557         }
558         else
559         {
560             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
561             return new ELTextComposite(ta);
562         }
563     }
564 
565     public static ELText[] parseAsArray(String in) throws ELException
566     {
567         return parseAsArray(null, null, in);
568     }
569     
570     public static ELText[] parseAsArray(ExpressionFactory fact, ELContext ctx, String in) throws ELException
571     {
572         char[] ca = in.toCharArray();
573         int i = 0;
574         char c = 0;
575         int len = ca.length;
576         int end = len - 1;
577         boolean esc = false;
578         int vlen = 0;
579 
580         StringBuffer buff = new StringBuffer(128);
581         List<ELText> text = new ArrayList<ELText>();
582         ELText t = null;
583         ValueExpression ve = null;
584 
585         while (i < len)
586         {
587             c = ca[i];
588             if ('\\' == c)
589             {
590                 esc = !esc;
591                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
592                 {
593                     i++;
594                     continue;
595                 }
596             }
597             else if (!esc && ('$' == c || '#' == c))
598             {
599                 if (i < end)
600                 {
601                     if ('{' == ca[i + 1])
602                     {
603                         if (buff.length() > 0)
604                         {
605                             text.add(new ELText(buff.toString()));
606                             buff.setLength(0);
607                         }
608                         vlen = findVarLength(ca, i);
609                         if (ctx != null && fact != null)
610                         {
611                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
612                             t = new ELCacheableTextVariable(ve);
613                         }
614                         else
615                         {
616                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
617                         }
618                         text.add(t);
619                         i += vlen;
620                         continue;
621                     }
622                 }
623             }
624             esc = false;
625             buff.append(c);
626             i++;
627         }
628 
629         if (buff.length() > 0)
630         {
631             text.add(new ELText(buff.toString()));
632             buff.setLength(0);
633         }
634 
635         if (text.isEmpty())
636         {
637             return null;
638         }
639         else if (text.size() == 1)
640         {
641             return new ELText[]{text.get(0)};
642         }
643         else
644         {
645             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
646             return ta;
647         }
648     }
649     
650     public static boolean isLiteral(ExpressionFactory fact, ELContext ctx, String in) throws ELException
651     {
652         char[] ca = in.toCharArray();
653         int i = 0;
654         char c = 0;
655         int len = ca.length;
656         int end = len - 1;
657         boolean esc = false;
658         //int vlen = 0;
659 
660         while (i < len)
661         {
662             c = ca[i];
663             if ('\\' == c)
664             {
665                 esc = !esc;
666                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
667                 {
668                     i++;
669                     continue;
670                 }
671             }
672             else if (!esc && ('$' == c || '#' == c))
673             {
674                 if (i < end)
675                 {
676                     if ('{' == ca[i + 1])
677                     {
678                         //vlen = findVarLength(ca, i);
679                         //In this point we have at least 1 EL expression, so it is not literal
680                         return false;
681                     }
682                 }
683             }
684             esc = false;
685             i++;
686         }
687         return true;
688     }
689 
690     private static int findVarLength(char[] ca, int s) throws ELException
691     {
692         int i = s;
693         int len = ca.length;
694         char c = 0;
695         int str = 0;
696         int nest = 0;
697         while (i < len)
698         {
699             c = ca[i];
700             if ('\\' == c && i < len - 1)
701             {
702                 i++;
703             }
704             else if ('\'' == c || '"' == c)
705             {
706                 if (str == c)
707                 {
708                     str = 0;
709                 }
710                 else
711                 {
712                     str = c;
713                 }
714             }
715             else if ('{' == c && str == 0)
716             {
717                 ++nest;
718             }
719             else if ('}' == c && str == 0 && nest > 1)
720             {
721                 --nest;
722             }
723             else if (str == 0 && ('}' == c && nest == 1))
724             {
725                 return i - s + 1;
726             }
727             i++;
728         }
729         throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));
730     }
731 
732 }