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