View Javadoc

1   /**
2    * Copyright 2007 Art Technology Group, Inc (ATG)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    *
7    * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and limitations under the License.
13   */
14  
15  package atg.taglib.json;
16  
17  
18  import atg.taglib.json.util.JSONArray;
19  
20  import java.io.IOException;
21  import java.io.StringWriter;
22  import java.lang.reflect.Array;
23  import java.text.MessageFormat;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Iterator;
28  import java.util.Map;
29  
30  import javax.servlet.jsp.JspException;
31  import javax.servlet.jsp.tagext.JspFragment;
32  
33  
34  /**
35   * Tag to represent a JSON array. The body of this tag should declare the template that will
36   * be used to represent each element in the array. Each element in the 'items' collection will
37   * be iterated over, and the body of the tag used to render each item in the array.
38   * <br/>
39   * This tag should contain a single json:object element if each array element is an object,
40   * otherwise it should contain textual content, which will create an array of Strings
41   *
42   * @author James Wiltshire
43   * @version $Id$
44   */
45  
46  public class JsonArrayTag extends JsonBaseTag
47  {
48    protected String mVar;
49  
50    /**
51     * Gets the Var
52     * @return the Var
53     */
54    public String getVar()
55    {
56      return mVar;
57    }
58  
59    /**
60     * Sets the Var
61     *
62     * @param pVar The Var to set
63     */
64    public void setVar(String pVar)
65    {
66      mVar = pVar;
67    }
68    
69    protected Object mItems; // The raw items as set on the tag
70  
71    /**
72     * Gets the Items
73     * @return the Items
74     */
75    public Object getItems()
76    {
77      return mItems;
78    }
79  
80    /**
81     * Sets the Items
82     *
83     * @param pItems The Items to set
84     */
85    public void setItems(Object pItems)
86    {
87      mItems = pItems;
88      mItemsPropertySet=true;
89    }
90    
91    /**
92     * Raw items coerced to a Collection
93     */
94    protected Collection mItemsCollection;
95    
96    /**
97     * Flag to signify whether the items property was explicitly set
98     */
99    protected boolean mItemsPropertySet=false;  
100   
101   
102   /**
103    * Process the tag
104    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
105    *
106    * @throws JspException
107    */
108   public void doTag() throws JspException{
109     JspFragment body = getJspBody();
110     
111     // Ensure that we have a 'var' attribute if we have a collection of items and a tag body
112     if (mItemsPropertySet && getVar()==null && body !=null){
113       throw new JspException(Messages.getString("atg.taglib.json.error.array.1"));
114     }
115         
116     try{         
117       // Create a new JSON Array and push it on the top of the stack
118       JSONArray array = new JSONArray();
119       JsonEntity entity = new JsonEntity(array);
120       getEntityStack().push(entity);
121       
122       if (mItemsPropertySet){
123         coerceItemsToCollection();
124         
125         // An items property has been set - if there's anything in it, then iterate over the items
126         if (!mItemsCollection.isEmpty()){
127           iterateOverItems(array);
128         }      
129       }
130       else {
131         // No collection, so just process the body which should add items to the array
132         // Writer passed here is used to prevent any body content being written to output stream
133         if (body!=null){
134           StringWriter writer = new StringWriter();
135           body.invoke(writer);
136         }
137       }
138       
139       // Pop the array entity off of the stack
140       getEntityStack().pop();
141       
142       // Now add the newly created array to the parent JSON entity
143       processTagEnd(entity);    
144     }
145     catch (JspException e){
146       // Just rethrow if we got a JspException - it's probably from a nested tag - no new
147       // info for us to add here.
148       throw e;
149     }
150     catch (Exception e){
151       throw new JspException(Messages.getString("atg.taglib.json.error.array.0"),e);
152     }
153   }  
154 
155   /**
156    * Iterate over the items collection, adding each item to the array either directly or
157    * by invoking the tag body.
158    *
159    * @param pArray The JSON array to add each item to
160    * @throws JspException
161    * @throws IOException
162    */
163   private void iterateOverItems(JSONArray pArray) throws JspException, IOException
164   {
165     JspFragment body = getJspBody();
166     
167     // Iterator over 'items' collection
168     Iterator it = mItemsCollection.iterator();
169     while (it.hasNext()){
170       Object currentItem=it.next();
171               
172       if (body==null){
173         // If no body has been specified, then just add each item in the 
174         // collection directly to the array
175         pArray.add(currentItem);
176       }
177       else{
178         // A body has been set, so invoke the body for each item in the collection
179       
180         // Set the current item in page scope, using value of 'var' as the attribute name
181         getJspContext().setAttribute(getVar(), currentItem);
182         int arraySizeBeforeInvokingBody = pArray.size();
183         
184         // Invoke the tag body, capturing the output to a writer
185         StringWriter writer = new StringWriter();
186         body.invoke(writer);
187                
188         // Check to see whether any objects were added to the array by the tag body
189         // If an object, property or data tag was encountered, it will have added a new 
190         // element to the array (which is currently on the top of the stack)
191         if (pArray.size() == arraySizeBeforeInvokingBody){
192           // Size is the same - nothing has been added to the JSONArray. Treat the tag
193           // body as text
194           Object value=writer.toString();
195           
196           // Trim and xml-escape the value
197           value=trimAndEscapeValue(value);
198           
199           // Add the trimmed string value to the array
200           pArray.add(value);
201         }
202         
203         // Cleanup 'var' attribute
204         getJspContext().removeAttribute(getVar());
205       }                   
206     } // End of 'items' iteration
207   }
208   
209   /**
210    * Coerce the raw items object that has been set to a Collection that we
211    * can subsequently iterate over
212    * 
213    * @throws JspException If unable to coerce the raw items to a Collection
214    *
215    */
216   private void coerceItemsToCollection() throws JspException
217   {
218     Object o = getItems();
219     Collection result;
220     if (o==null){
221       // Items property on the tag has been set, but the value set with is NULL, so just
222       // use an empty list
223       result = new ArrayList();
224     }       
225     else if (
226         // Primitive arrays
227         (o instanceof boolean[]) ||
228         (o instanceof byte[]) ||
229         (o instanceof char[]) ||
230         (o instanceof short[]) ||
231         (o instanceof int[]) ||
232         (o instanceof long[]) ||
233         (o instanceof float[]) ||
234         (o instanceof double[])) {
235       result = convertArrayToList(o);
236     }
237     else if (o instanceof Object[]){
238       result = Arrays.asList((Object[])o);
239     }
240     else if (o instanceof Collection){
241       result = (Collection)o;
242     }
243     else if (o instanceof Map){
244       // Use the values of a map - ordering may be undetermined depending on concrete Map implementation used
245       result = ((Map)o).values();
246     }
247     else if (o instanceof String){
248       // Comma-separated string
249       result = Arrays.asList(((String)o).split(","));
250     }
251     else {
252       String msg=MessageFormat.format(Messages.getString("atg.taglib.json.error.array.2"), 
253           new Object[]{o.getClass()});
254       throw new JspException(msg);
255     }
256     
257     mItemsCollection=result;
258   }
259   
260   /**
261    * Convert a primitive array to a Collection
262    *
263    * @param pArray The primitive array to convert
264    * @return A Collection containing wrapper objects of all the primitive array elements
265    */
266   private Collection convertArrayToList(Object pArray){
267     if (pArray==null){
268       // Treat null as an empty list
269       return new ArrayList();
270     }
271     int length = Array.getLength(pArray);
272     if (length == 0) {
273       // Empty array, so just return an empty list
274       return new ArrayList();
275     }
276     Class wrapperType = Array.get(pArray, 0).getClass();
277     Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
278     for (int i = 0; i < length; i++) {
279       newArray[i] = Array.get(pArray, i);
280     }
281     return Arrays.asList(newArray);
282   }
283 }
284