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.io.Serializable;
23  import java.lang.reflect.Array;
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.el.ELException;
30  import javax.el.ValueExpression;
31  import javax.faces.FacesException;
32  import javax.faces.application.StateManager;
33  import javax.faces.component.UIComponent;
34  import javax.faces.event.PhaseId;
35  import javax.faces.view.facelets.FaceletContext;
36  import javax.faces.view.facelets.FaceletException;
37  import javax.faces.view.facelets.TagAttribute;
38  import javax.faces.view.facelets.TagAttributeException;
39  import javax.faces.view.facelets.TagConfig;
40  import javax.faces.view.facelets.TagHandler;
41  
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletAttribute;
43  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletTag;
44  import org.apache.myfaces.util.ExternalSpecifications;
45  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
46  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
47  import org.apache.myfaces.view.facelets.PageContext;
48  import org.apache.myfaces.view.facelets.el.FaceletStateValueExpression;
49  import org.apache.myfaces.view.facelets.el.FaceletStateValueExpressionUEL;
50  import org.apache.myfaces.view.facelets.tag.ComponentContainerHandler;
51  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
52  import org.apache.myfaces.view.facelets.tag.jsf.FaceletState;
53  
54  /**
55   * The basic iteration tag, accepting many different
56   * collection types and supporting subsetting and other
57   * functionality
58   * 
59   * @author Jacob Hookom
60   * @author Andrew Robinson
61   * @version $Id$
62   */
63  @JSFFaceletTag(name="c:forEach")
64  public final class ForEachHandler extends TagHandler implements ComponentContainerHandler
65  {
66  
67      private static class ArrayIterator implements Iterator<Object>
68      {
69  
70          protected final Object array;
71  
72          protected int i;
73  
74          protected final int len;
75  
76          public ArrayIterator(Object src)
77          {
78              this.i = 0;
79              this.array = src;
80              this.len = Array.getLength(src);
81          }
82  
83          public boolean hasNext()
84          {
85              return this.i < this.len;
86          }
87  
88          public Object next()
89          {
90              return Array.get(this.array, this.i++);
91          }
92  
93          public void remove()
94          {
95              throw new UnsupportedOperationException();
96          }
97      }
98  
99      /**
100      * If items specified:
101      * Iteration begins at the item located at the
102      * specified index. First item of the collection has
103      * index 0.
104      * If items not specified:
105      * Iteration begins with index set at the value
106      * specified.
107      */
108     @JSFFaceletAttribute(className="int")
109     private final TagAttribute begin;
110 
111     /**
112      * If items specified:
113      * Iteration ends at the item located at the
114      * specified index (inclusive).
115      * If items not specified:
116      * Iteration ends when index reaches the value
117      * specified.
118      */
119     @JSFFaceletAttribute(className="int")
120     private final TagAttribute end;
121 
122     /**
123      * Collection of items to iterate over.
124      */
125     @JSFFaceletAttribute(className="javax.el.ValueExpression")
126     private final TagAttribute items;
127 
128     /**
129      * Iteration will only process every step items of
130      * the collection, starting with the first one.
131      */
132     @JSFFaceletAttribute(className="int")
133     private final TagAttribute step;
134 
135     private final TagAttribute tranzient;
136 
137     /**
138      * Name of the exported scoped variable for the
139      * current item of the iteration. This scoped
140      * variable has nested visibility. Its type depends
141      * on the object of the underlying collection.
142      */
143     @JSFFaceletAttribute(className="java.lang.String")
144     private final TagAttribute var;
145 
146     /**
147      * Name of the exported scoped variable for the
148      * status of the iteration. 
149      */
150     @JSFFaceletAttribute(className="java.lang.String")
151     private final TagAttribute varStatus;
152 
153     /**
154      * @param config
155      */
156     public ForEachHandler(TagConfig config)
157     {
158         super(config);
159         this.items = this.getAttribute("items");
160         this.var = this.getAttribute("var");
161         this.begin = this.getAttribute("begin");
162         this.end = this.getAttribute("end");
163         this.step = this.getAttribute("step");
164         this.varStatus = this.getAttribute("varStatus");
165         this.tranzient = this.getAttribute("transient");
166 
167         if (this.items == null && this.begin != null && this.end == null)
168         {
169             throw new TagAttributeException(this.tag, this.begin,
170                                             "If the 'items' attribute is not specified, but the 'begin' attribute is, "
171                                             + "then the 'end' attribute is required");
172         }
173     }
174 
175     public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
176             ELException
177     {
178         int e = this.getEnd(ctx);
179         Object src = null;
180         ValueExpression srcVE = null;
181         if (this.items != null)
182         {
183             srcVE = this.items.getValueExpression(ctx, Object.class);
184             src = srcVE.getValue(ctx);
185         }
186         else
187         {
188             byte[] b = new byte[e + 1];
189             for (int i = 0; i < b.length; i++)
190             {
191                 b[i] = (byte) i;
192             }
193             src = b;
194         }
195         FaceletCompositionContext fcc = FaceletCompositionContext.getCurrentInstance(ctx);
196         AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
197         // Just increment one number to ensure the prefix doesn't conflict later if two
198         // c:forEach are close between each other. Note c:forEach is different from
199         // c:if tag and doesn't require a section because c:forEach requires to provide
200         // multiple sections starting with a specified "base" related to the element
201         // position and value in the collection.
202         fcc.incrementUniqueComponentId();
203         String uniqueId = actx.generateUniqueFaceletTagId(fcc.generateUniqueId(), tagId);
204         if (src != null)
205         {
206             PageContext pctx = actx.getPageContext();
207             // c:forEach is special because it requires FaceletState even if no pss is used.
208             FaceletState restoredFaceletState = ComponentSupport.getFaceletState(ctx, parent, false);
209             IterationState restoredSavedOption = (restoredFaceletState == null) ? null : 
210                 (IterationState) restoredFaceletState.getState(uniqueId);
211 
212             if (restoredSavedOption != null)
213             {            
214                 if (!PhaseId.RESTORE_VIEW.equals(ctx.getFacesContext().getCurrentPhaseId()))
215                 {
216                     // Refresh, evaluate and synchronize state
217                     applyOnRefresh(ctx, fcc, pctx, parent, uniqueId, src, srcVE, restoredSavedOption);
218                 }
219                 else
220                 {
221                     // restore view, don't record iteration, use the saved value
222                     applyOnRestore(ctx, fcc, pctx, parent, uniqueId, src, srcVE, restoredSavedOption);
223                 }
224             }
225             else
226             {
227                 // First time         
228                 applyFirstTime(ctx, fcc, pctx, parent, uniqueId, src, srcVE);
229             }
230         }
231 
232         if (fcc.isUsingPSSOnThisView() && fcc.isRefreshTransientBuildOnPSS() && !fcc.isRefreshingTransientBuild())
233         {
234             //Mark the parent component to be saved and restored fully.
235             ComponentSupport.markComponentToRestoreFully(ctx.getFacesContext(), parent);
236         }
237         if (fcc.isDynamicComponentSection())
238         {
239             ComponentSupport.markComponentToRefreshDynamically(ctx.getFacesContext(), parent);
240         }
241     }
242     
243     private void setVar(FaceletContext ctx, UIComponent parent,
244         String uniqueId, String base, boolean t, Object src, 
245         ValueExpression srcVE, Object value, String v, int i)
246     {
247         // set the var
248         if (v != null)
249         {
250             ValueExpression ve;
251             if (t || srcVE == null)
252             {
253                 if (value == null)
254                 {
255                     ve = null;
256                 }
257                 else
258                 {
259                     ve = ctx.getExpressionFactory().createValueExpression(
260                                 value, Object.class);
261                 }
262             }
263             else
264             {
265                 ve = this.getVarExpr(srcVE, src, value, i);
266             }
267             setVar(ctx, parent, uniqueId, base, v, ve, srcVE);
268         }
269     }
270     
271     private void setVar(FaceletContext ctx, UIComponent parent,
272         String uniqueId, String base, String v, ValueExpression ve, ValueExpression srcVE)
273     {
274         AbstractFaceletContext actx = ((AbstractFaceletContext) ctx);
275         PageContext pctx = actx.getPageContext();
276         //if (ELExpressionCacheMode.alwaysRecompile.equals(actx.getELExpressionCacheMode()))
277         //{
278         if (srcVE != null)
279         {
280             FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
281             faceletState.putBinding(uniqueId, base, ve);
282 
283             //Put the indirect EL into context
284             ValueExpression fve;
285             if (ExternalSpecifications.isUnifiedELAvailable())
286             {
287                 fve = new FaceletStateValueExpressionUEL(uniqueId, base);
288             }
289             else
290             {
291                 fve = new FaceletStateValueExpression(uniqueId, base);
292             }
293             pctx.getAttributes().put(v, fve);
294         }
295         else
296         {
297             pctx.getAttributes().put(v, ve);
298         }
299     }
300     
301     private void applyFirstTime(FaceletContext ctx, FaceletCompositionContext fcc, PageContext pctx, 
302         UIComponent parent, String uniqueId, Object src, ValueExpression srcVE) throws IOException
303     {
304         int s = this.getBegin(ctx);
305         int e = this.getEnd(ctx);
306         int m = this.getStep(ctx);
307         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
308         Integer eO = this.end != null ? Integer.valueOf(e) : null;
309         Integer mO = this.step != null ? Integer.valueOf(m) : null;
310         boolean t = this.getTransient(ctx);
311         IterationState iterationState = new IterationState();
312 
313         boolean serializableValues = true;
314         Iterator<?> itr = this.toIterator(src);
315         if (itr != null)
316         {
317             int i = 0;
318 
319             // move to start
320             while (i < s && itr.hasNext())
321             {
322                 itr.next();
323                 i++;
324             }
325 
326             String v = this.getVarName(ctx);
327             String vs = this.getVarStatusName(ctx);
328             ValueExpression vO = this.capture(v, pctx);
329             ValueExpression vsO = this.capture(vs, pctx);
330             int mi = 0;
331             Object value = null;
332             try
333             {
334                 boolean first = true;
335                 while (i <= e && itr.hasNext())
336                 {
337                     value = itr.next();
338 
339                     // first time, use the counter
340                     Integer count = iterationState.getCounter();
341                     String base = count.toString();
342                     iterationState.setCounter(count+1);
343 
344                     if (value instanceof Serializable)
345                     {
346                         iterationState.getValueList().add(
347                             new Object[]{base, value, i});
348                     }
349                     else
350                     {
351                         serializableValues = false;
352                     }
353 
354                     try
355                     {
356                         fcc.startComponentUniqueIdSection(base);
357 
358                         setVar(ctx, parent, uniqueId, base, t, src, srcVE, value, v, i);
359                         boolean last = !itr.hasNext();
360                         // set the varStatus
361                         if (vs != null)
362                         {
363                             IterationStatus itrS = new IterationStatus(first, last, i, sO, eO, mO, value);
364                             ValueExpression ve;
365                             if (t || srcVE == null)
366                             {
367                                 if (srcVE == null)
368                                 {
369                                     ve = null;
370                                 }
371                                 else
372                                 {
373                                     ve = ctx.getExpressionFactory().createValueExpression(
374                                                 itrS, Object.class);
375                                 }
376                             }
377                             else
378                             {
379                                 ve = new IterationStatusExpression(itrS);
380                             }
381                             setVar(ctx, parent, uniqueId, base+"_vs", vs, ve, srcVE);
382                         }
383 
384                         // execute body
385                         this.nextHandler.apply(ctx, parent);
386                     }
387                     finally
388                     {
389                         fcc.endComponentUniqueIdSection(base);
390                     }
391 
392                     // increment steps
393                     mi = 1;
394                     while (mi < m && itr.hasNext())
395                     {
396                         itr.next();
397                         mi++;
398                         i++;
399                     }
400                     i++;
401 
402                     first = false;
403                 }
404             }
405             finally
406             {
407                 removeVarAndVarStatus(pctx, v, vO, vs, vsO);
408             }
409         }
410         if (serializableValues)
411         {
412             FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
413             faceletState.putState(uniqueId, iterationState);
414         }
415     }
416     
417     private void applyOnRestore(FaceletContext ctx, FaceletCompositionContext fcc, PageContext pctx, 
418         UIComponent parent, String uniqueId, Object src, ValueExpression srcVE, IterationState restoredSavedOption)
419         throws IOException
420     {
421         int s = this.getBegin(ctx);
422         int e = this.getEnd(ctx);
423         int m = this.getStep(ctx);
424         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
425         Integer eO = this.end != null ? Integer.valueOf(e) : null;
426         Integer mO = this.step != null ? Integer.valueOf(m) : null;
427         boolean t = this.getTransient(ctx);
428 
429         // restore view, don't record iteration, use the saved value
430         String v = this.getVarName(ctx);
431         String vs = this.getVarStatusName(ctx);
432         ValueExpression vO = this.capture(v, pctx);
433         ValueExpression vsO = this.capture(vs, pctx);
434         Object value = null;
435         try
436         {
437             int size = restoredSavedOption.getValueList().size();
438             for (int si = 0; si < size; si++)
439             {
440                 Object[] stateValue = restoredSavedOption.getValueList().get(si);
441                 value = stateValue[1];
442                 String base = (String) stateValue[0];
443 
444                 try
445                 {
446                     fcc.startComponentUniqueIdSection(base);
447 
448                     setVar(ctx, parent, uniqueId, base, t, src, srcVE, value, v, (Integer) stateValue[2]);
449 
450                     boolean first = (si == 0);
451                     boolean last = (si == size-1);
452                     int i = (Integer)stateValue[2];
453                     // set the varStatus
454                     if (vs != null)
455                     {
456                         IterationStatus itrS = new IterationStatus(first, last, i, sO, eO, mO, value);
457                         ValueExpression ve;
458                         if (t || srcVE == null)
459                         {
460                             if (srcVE == null)
461                             {
462                                 ve = null;
463                             }
464                             else
465                             {
466                                 ve = ctx.getExpressionFactory().createValueExpression(
467                                             itrS, Object.class);
468                             }
469                         }
470                         else
471                         {
472                             ve = new IterationStatusExpression(itrS);
473                         }
474                         setVar(ctx, parent, uniqueId, base+"_vs", vs, ve, srcVE);
475                     }
476 
477                     // execute body
478                     this.nextHandler.apply(ctx, parent);
479                 }
480                 finally
481                 {
482                     fcc.endComponentUniqueIdSection(base);
483                 }
484             }
485         }
486         finally
487         {
488             removeVarAndVarStatus(pctx, v, vO, vs, vsO);
489         }
490     }
491     
492     private void applyOnRefresh(FaceletContext ctx, FaceletCompositionContext fcc, PageContext pctx, 
493         UIComponent parent, String uniqueId, Object src, ValueExpression srcVE, IterationState restoredSavedOption)
494         throws IOException
495     {
496         int s = this.getBegin(ctx);
497         int e = this.getEnd(ctx);
498         int m = this.getStep(ctx);
499         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
500         Integer eO = this.end != null ? Integer.valueOf(e) : null;
501         Integer mO = this.step != null ? Integer.valueOf(m) : null;
502         boolean t = this.getTransient(ctx);
503 
504         // Refresh, evaluate and synchronize state
505         Iterator<?> itr = this.toIterator(src);
506         IterationState iterationState = new IterationState();
507         iterationState.setCounter(restoredSavedOption.getCounter());
508         if (itr != null)
509         {
510             int i = 0;
511 
512             // move to start
513             while (i < s && itr.hasNext())
514             {
515                 itr.next();
516                 i++;
517             }
518 
519             String v = this.getVarName(ctx);
520             String vs = this.getVarStatusName(ctx);
521             ValueExpression vO = this.capture(v, pctx);
522             ValueExpression vsO = this.capture(vs, pctx);
523             int mi = 0;
524             Object value = null;
525             int stateIndex = 0;
526             try
527             {
528                 boolean first = true;
529                 while (i <= e && itr.hasNext())
530                 {
531                     value = itr.next();
532                     Object[] stateValue = null; /*restoredSavedOption.getValueList().get(stateIndex);*/
533                     String base = null;
534                     boolean found = false;
535 
536                     // The important thing here is use the same base for the generated component ids
537                     // for each element in the iteration that was used on the restore. To do that
538                     // 
539                     int stateIndexCheck = stateIndex;
540                     for (; stateIndexCheck < restoredSavedOption.getValueList().size(); stateIndexCheck++)
541                     {
542                         stateValue = restoredSavedOption.getValueList().get(stateIndexCheck);
543                         if (value.equals(stateValue[1]))
544                         {
545                             found = true;
546                             break;
547                         }
548                     }
549                     if (found)
550                     {
551                         stateIndex = stateIndexCheck;
552                         base = (String) stateValue[0];
553                         stateIndex++;
554                     }
555                     else
556                     {
557                         // No state, added item, create new count
558                         Integer count = iterationState.getCounter();
559                         base = count.toString();
560                         iterationState.setCounter(count+1);
561                         stateValue = null;
562                     }
563 
564                     if (value instanceof Serializable)
565                     {
566                         iterationState.getValueList().add(
567                             new Object[]{base, value, i});
568                     }
569 
570                     try
571                     {
572                         fcc.startComponentUniqueIdSection(base);
573 
574                         setVar(ctx, parent, uniqueId, base, t, src, srcVE, value, v, i);
575 
576                         boolean last = !itr.hasNext();
577                         // set the varStatus
578                         if (vs != null)
579                         {
580                             IterationStatus itrS = new IterationStatus(first, last, i, sO, eO, mO, value);
581                             ValueExpression ve;
582                             if (t || srcVE == null)
583                             {
584                                 if (srcVE == null)
585                                 {
586                                     ve = null;
587                                 }
588                                 else
589                                 {
590                                     ve = ctx.getExpressionFactory().createValueExpression(
591                                                 itrS, Object.class);
592                                 }
593                             }
594                             else
595                             {
596                                 ve = new IterationStatusExpression(itrS);
597                             }
598                             setVar(ctx, parent, uniqueId, base+"_vs", vs, ve, srcVE);
599                         }
600                         //setVarStatus(ctx, pctx, t, sO, eO, mO, srcVE, value, vs, first, !itr.hasNext(), i);
601 
602                         // execute body
603                         boolean markInitialState = (stateValue == null);// !restoredSavedOption.equals(i)
604                         boolean oldMarkInitialState = false;
605                         Boolean isBuildingInitialState = null;
606                         try
607                         {
608                             if (markInitialState && fcc.isUsingPSSOnThisView())
609                             {
610                                 //set markInitialState flag
611                                 oldMarkInitialState = fcc.isMarkInitialState();
612                                 fcc.setMarkInitialState(true);
613                                 isBuildingInitialState = (Boolean) ctx.getFacesContext().getAttributes().put(
614                                         StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
615                             }                                
616                             this.nextHandler.apply(ctx, parent);
617                         }
618                         finally
619                         {
620                             if (markInitialState && fcc.isUsingPSSOnThisView())
621                             {
622                                 //unset markInitialState flag
623                                 if (isBuildingInitialState == null)
624                                 {
625                                     ctx.getFacesContext().getAttributes().remove(
626                                             StateManager.IS_BUILDING_INITIAL_STATE);
627                                 }
628                                 else
629                                 {
630                                     ctx.getFacesContext().getAttributes().put(
631                                             StateManager.IS_BUILDING_INITIAL_STATE, isBuildingInitialState);
632                                 }
633                                 fcc.setMarkInitialState(oldMarkInitialState);
634                             }
635                         }
636                     }
637                     finally
638                     {
639                         fcc.endComponentUniqueIdSection(base);
640                     }
641 
642                     // increment steps
643                     mi = 1;
644                     while (mi < m && itr.hasNext())
645                     {
646                         itr.next();
647                         mi++;
648                         i++;
649                     }
650                     i++;
651 
652                     first = false;
653                 }
654             }
655             finally
656             {
657                 removeVarAndVarStatus(pctx, v, vO, vs, vsO);
658             }
659         }
660         FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
661         faceletState.putState(uniqueId, iterationState);
662     }
663     
664     private void removeVarAndVarStatus(PageContext pctx, String v, ValueExpression vO, String vs, ValueExpression vsO)
665     {
666         //Remove them from PageContext
667         if (v != null)
668         {
669             pctx.getAttributes().put(v, vO);
670         }
671         else
672         {
673             pctx.getAttributes().remove(v);
674         }
675         if (vs != null)
676         {
677             pctx.getAttributes().put(vs, vsO);
678         }
679         else
680         {
681             pctx.getAttributes().remove(vs);
682         }
683     }
684 
685     private ValueExpression capture(String name, PageContext pctx)
686     {
687         if (name != null)
688         {
689             return pctx.getAttributes().put(name, null);
690         }
691         return null;
692     }
693 
694     private int getBegin(FaceletContext ctx)
695     {
696         if (this.begin != null)
697         {
698             return this.begin.getInt(ctx);
699         }
700         return 0;
701     }
702 
703     private int getEnd(FaceletContext ctx)
704     {
705         if (this.end != null)
706         {
707             return this.end.getInt(ctx);
708         }
709         return Integer.MAX_VALUE - 1; // hotspot bug in the JVM
710     }
711 
712     private int getStep(FaceletContext ctx)
713     {
714         if (this.step != null)
715         {
716             return this.step.getInt(ctx);
717         }
718         return 1;
719     }
720 
721     private boolean getTransient(FaceletContext ctx)
722     {
723         if (this.tranzient != null)
724         {
725             return this.tranzient.getBoolean(ctx);
726         }
727         return false;
728     }
729 
730     private ValueExpression getVarExpr(ValueExpression ve, Object src, Object value, int i)
731     {
732         if (src instanceof List || src.getClass().isArray())
733         {
734             //return new IndexedValueExpression(ve, i);
735             return new IteratedValueExpression(ve, value);
736         }
737         else if (src instanceof Map && value instanceof Map.Entry)
738         {
739             return new MappedValueExpression(ve, (Map.Entry) value);
740         }
741         else if (src instanceof Collection)
742         {
743             return new IteratedValueExpression(ve, value);
744         }
745         throw new IllegalStateException("Cannot create VE for: " + src);
746     }
747 
748     private String getVarName(FaceletContext ctx)
749     {
750         if (this.var != null)
751         {
752             return this.var.getValue(ctx);
753         }
754         return null;
755     }
756 
757     private String getVarStatusName(FaceletContext ctx)
758     {
759         if (this.varStatus != null)
760         {
761             return this.varStatus.getValue(ctx);
762         }
763         return null;
764     }
765 
766     private Iterator<?> toIterator(Object src)
767     {
768         if (src == null)
769         {
770             return null;
771         }
772         else if (src instanceof Collection)
773         {
774             return ((Collection<?>) src).iterator();
775         }
776         else if (src instanceof Map)
777         {
778             return ((Map<?, ?>) src).entrySet().iterator();
779         }
780         else if (src.getClass().isArray())
781         {
782             return new ArrayIterator(src);
783         }
784         else
785         {
786             throw new TagAttributeException(this.tag, this.items,
787                     "Must evaluate to a Collection, Map, Array, or null.");
788         }
789     }
790 
791 }