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.tag.jstl.core;
20  
21  import java.io.IOException;
22  import java.lang.reflect.Array;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.el.ELException;
29  import javax.el.ValueExpression;
30  import javax.faces.FacesException;
31  import javax.faces.component.UIComponent;
32  import javax.faces.view.facelets.FaceletContext;
33  import javax.faces.view.facelets.FaceletException;
34  import javax.faces.view.facelets.TagAttribute;
35  import javax.faces.view.facelets.TagAttributeException;
36  import javax.faces.view.facelets.TagConfig;
37  import javax.faces.view.facelets.TagHandler;
38  
39  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
40  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
41  import org.apache.myfaces.view.facelets.PageContext;
42  import org.apache.myfaces.view.facelets.tag.ComponentContainerHandler;
43  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
44  
45  /**
46   * The basic iteration tag, accepting many different
47   * collection types and supporting subsetting and other
48   * functionality
49   * 
50   * NOTE: This implementation is provided for compatibility reasons and
51   * it is considered faulty. It is enabled using
52   * org.apache.myfaces.STRICT_JSF_2_FACELETS_COMPATIBILITY web config param.
53   * Don't use it if EL expression caching is enabled.
54   * 
55   * @author Jacob Hookom
56   * @author Andrew Robinson
57   * @version $Id$
58   */
59  //@JSFFaceletTag(name="c:forEach")
60  public final class LegacyForEachHandler extends TagHandler implements ComponentContainerHandler
61  {
62  
63      private static class ArrayIterator implements Iterator<Object>
64      {
65  
66          protected final Object array;
67  
68          protected int i;
69  
70          protected final int len;
71  
72          public ArrayIterator(Object src)
73          {
74              this.i = 0;
75              this.array = src;
76              this.len = Array.getLength(src);
77          }
78  
79          @Override
80          public boolean hasNext()
81          {
82              return this.i < this.len;
83          }
84  
85          @Override
86          public Object next()
87          {
88              return Array.get(this.array, this.i++);
89          }
90  
91          @Override
92          public void remove()
93          {
94              throw new UnsupportedOperationException();
95          }
96      }
97  
98      /**
99       * If items specified:
100      * Iteration begins at the item located at the
101      * specified index. First item of the collection has
102      * index 0.
103      * If items not specified:
104      * Iteration begins with index set at the value
105      * specified.
106      */
107     //@JSFFaceletAttribute(className="int")
108     private final TagAttribute begin;
109 
110     /**
111      * If items specified:
112      * Iteration ends at the item located at the
113      * specified index (inclusive).
114      * If items not specified:
115      * Iteration ends when index reaches the value
116      * specified.
117      */
118     //@JSFFaceletAttribute(className="int")
119     private final TagAttribute end;
120 
121     /**
122      * Collection of items to iterate over.
123      */
124     //@JSFFaceletAttribute(className="javax.el.ValueExpression")
125     private final TagAttribute items;
126 
127     /**
128      * Iteration will only process every step items of
129      * the collection, starting with the first one.
130      */
131     //@JSFFaceletAttribute(className="int")
132     private final TagAttribute step;
133 
134     private final TagAttribute tranzient;
135 
136     /**
137      * Name of the exported scoped variable for the
138      * current item of the iteration. This scoped
139      * variable has nested visibility. Its type depends
140      * on the object of the underlying collection.
141      */
142     //@JSFFaceletAttribute(className="java.lang.String")
143     private final TagAttribute var;
144 
145     /**
146      * Name of the exported scoped variable for the
147      * status of the iteration. 
148      */
149     //@JSFFaceletAttribute(className="java.lang.String")
150     private final TagAttribute varStatus;
151 
152     /**
153      * @param config
154      */
155     public LegacyForEachHandler(TagConfig config)
156     {
157         super(config);
158         this.items = this.getAttribute("items");
159         this.var = this.getAttribute("var");
160         this.begin = this.getAttribute("begin");
161         this.end = this.getAttribute("end");
162         this.step = this.getAttribute("step");
163         this.varStatus = this.getAttribute("varStatus");
164         this.tranzient = this.getAttribute("transient");
165 
166         if (this.items == null && this.begin != null && this.end == null)
167         {
168             throw new TagAttributeException(this.tag, this.begin,
169                                             "If the 'items' attribute is not specified, but the 'begin' attribute is, "
170                                             + "then the 'end' attribute is required");
171         }
172     }
173 
174     @Override
175     public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
176             ELException
177     {
178 
179         int s = this.getBegin(ctx);
180         int e = this.getEnd(ctx);
181         int m = this.getStep(ctx);
182         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
183         Integer eO = this.end != null ? Integer.valueOf(e) : null;
184         Integer mO = this.step != null ? Integer.valueOf(m) : null;
185 
186         boolean t = this.getTransient(ctx);
187         Object src = null;
188         ValueExpression srcVE = null;
189         if (this.items != null)
190         {
191             srcVE = this.items.getValueExpression(ctx, Object.class);
192             src = srcVE.getValue(ctx);
193         }
194         else
195         {
196             byte[] b = new byte[e + 1];
197             for (int i = 0; i < b.length; i++)
198             {
199                 b[i] = (byte) i;
200             }
201             src = b;
202         }
203         FaceletCompositionContext fcc = FaceletCompositionContext.getCurrentInstance(ctx);
204         if (src != null)
205         {
206             try
207             {
208                 fcc.startComponentUniqueIdSection();
209                 AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
210                 PageContext pctx = actx.getPageContext();
211                 Iterator<?> itr = this.toIterator(src);
212                 if (itr != null)
213                 {
214                     int i = 0;
215 
216                     // move to start
217                     while (i < s && itr.hasNext())
218                     {
219                         itr.next();
220                         i++;
221                     }
222 
223                     String v = this.getVarName(ctx);
224                     String vs = this.getVarStatusName(ctx);
225                     ValueExpression ve = null;
226                     ValueExpression vO = this.capture(v, pctx);
227                     ValueExpression vsO = this.capture(vs, pctx);
228                     int mi = 0;
229                     Object value = null;
230                     try
231                     {
232                         boolean first = true;
233                         while (i <= e && itr.hasNext())
234                         {
235                             value = itr.next();
236 
237                             // set the var
238                             if (v != null)
239                             {
240                                 if (t || srcVE == null)
241                                 {
242                                     if (value == null)
243                                     {
244                                         pctx.getAttributes().put(v, null);
245                                     }
246                                     else
247                                     {
248                                         pctx.getAttributes().put(v, 
249                                                 ctx.getExpressionFactory().createValueExpression(
250                                                     value, Object.class));
251                                     }
252                                 }
253                                 else
254                                 {
255                                     ve = this.getVarExpr(srcVE, src, value, i);
256                                     pctx.getAttributes().put(v, ve);
257                                 }
258                             }
259 
260                             // set the varStatus
261                             if (vs != null)
262                             {
263                                 IterationStatus itrS = new IterationStatus(first, !itr.hasNext(), i, sO, eO, mO, value);
264                                 if (t || srcVE == null)
265                                 {
266                                     if (srcVE == null)
267                                     {
268                                         pctx.getAttributes().put(vs, null);
269                                     }
270                                     else
271                                     {
272                                         pctx.getAttributes().put(vs, 
273                                                 ctx.getExpressionFactory().createValueExpression(
274                                                     itrS, Object.class));
275                                     }
276                                 }
277                                 else
278                                 {
279                                     ve = new IterationStatusExpression(itrS);
280                                     pctx.getAttributes().put(vs, ve);
281                                 }
282                             }
283 
284                             // execute body
285                             this.nextHandler.apply(ctx, parent);
286 
287                             // increment steps
288                             mi = 1;
289                             while (mi < m && itr.hasNext())
290                             {
291                                 itr.next();
292                                 mi++;
293                                 i++;
294                             }
295                             i++;
296 
297                             first = false;
298                         }
299                     }
300                     finally
301                     {
302                         //Remove them from PageContext
303                         if (v != null)
304                         {
305                             pctx.getAttributes().put(v, vO);
306                         }
307                         else
308                         {
309                             pctx.getAttributes().remove(v);
310                         }
311                         if (vs != null)
312                         {
313                             pctx.getAttributes().put(vs, vsO);
314                         }
315                         else
316                         {
317                             pctx.getAttributes().remove(vs);
318                         }
319                     }
320                 }
321             }
322             finally
323             {
324                 fcc.endComponentUniqueIdSection();
325             }
326         }
327 
328         if (fcc.isUsingPSSOnThisView() && fcc.isRefreshTransientBuildOnPSS() && !fcc.isRefreshingTransientBuild())
329         {
330             //Mark the parent component to be saved and restored fully.
331             ComponentSupport.markComponentToRestoreFully(ctx.getFacesContext(), parent);
332         }
333         if (fcc.isDynamicComponentSection())
334         {
335             ComponentSupport.markComponentToRefreshDynamically(ctx.getFacesContext(), parent);
336         }
337     }
338 
339     private final ValueExpression capture(String name, PageContext pctx)
340     {
341         if (name != null)
342         {
343             return pctx.getAttributes().put(name, null);
344         }
345         return null;
346     }
347 
348     private final int getBegin(FaceletContext ctx)
349     {
350         if (this.begin != null)
351         {
352             return this.begin.getInt(ctx);
353         }
354         return 0;
355     }
356 
357     private final int getEnd(FaceletContext ctx)
358     {
359         if (this.end != null)
360         {
361             return this.end.getInt(ctx);
362         }
363         return Integer.MAX_VALUE - 1; // hotspot bug in the JVM
364     }
365 
366     private final int getStep(FaceletContext ctx)
367     {
368         if (this.step != null)
369         {
370             return this.step.getInt(ctx);
371         }
372         return 1;
373     }
374 
375     private final boolean getTransient(FaceletContext ctx)
376     {
377         if (this.tranzient != null)
378         {
379             return this.tranzient.getBoolean(ctx);
380         }
381         return false;
382     }
383 
384     private final ValueExpression getVarExpr(ValueExpression ve, Object src, Object value, int i)
385     {
386         if (src instanceof List || src.getClass().isArray())
387         {
388             return new IndexedValueExpression(ve, i);
389         }
390         else if (src instanceof Map && value instanceof Map.Entry)
391         {
392             return new MappedValueExpression(ve, (Map.Entry) value);
393         }
394         else if (src instanceof Collection)
395         {
396             return new IteratedValueExpression(ve, value);
397         }
398         throw new IllegalStateException("Cannot create VE for: " + src);
399     }
400 
401     private final String getVarName(FaceletContext ctx)
402     {
403         if (this.var != null)
404         {
405             return this.var.getValue(ctx);
406         }
407         return null;
408     }
409 
410     private final String getVarStatusName(FaceletContext ctx)
411     {
412         if (this.varStatus != null)
413         {
414             return this.varStatus.getValue(ctx);
415         }
416         return null;
417     }
418 
419     private final Iterator<?> toIterator(Object src)
420     {
421         if (src == null)
422         {
423             return null;
424         }
425         else if (src instanceof Collection)
426         {
427             return ((Collection<?>) src).iterator();
428         }
429         else if (src instanceof Map)
430         {
431             return ((Map<?, ?>) src).entrySet().iterator();
432         }
433         else if (src.getClass().isArray())
434         {
435             return new ArrayIterator(src);
436         }
437         else
438         {
439             throw new TagAttributeException(this.tag, this.items,
440                     "Must evaluate to a Collection, Map, Array, or null.");
441         }
442     }
443 
444 }