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