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.trinidad.model;
20  
21  import java.lang.reflect.Array;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.List;
25  
26  import javax.faces.model.DataModel;
27  
28  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
29  import org.apache.myfaces.trinidad.util.CollectionUtils;
30  
31  /**
32   * Creates a MenuModel from a List of beans.
33   * To use this class you must have a tree of beans (or Maps).
34   * The structure of your tree must be a List (or array) of root beans, and each bean must have a
35   * getter method (or Map property) that returns the children List. All elements of your tree must be
36   * the same type.
37   * <P>
38   * Suppose you have a bean called EmpBean that contains the data for a particular employee. Suppose
39   * this bean has a method called getDirectReports() that returns a List of EmpBeans which are the
40   * direct reports of the employee. Suppose there is a List called "founders" which is the root list
41   * of EmpBeans. Now you can construct a MenuModel by calling:
42   * <pre>
43   * MenuModel model = new ChildPropertyMenuModel(founders, "directReports");
44   * </pre>
45   * Bean rules will be used to find an appropriate getter method for the "directReports" property.
46   * java.util.Maps are also supported instead of beans.
47   * <p>
48   * Example: Given the following class:
49   * <pre>
50   * public class Person
51   * {
52   *    public Person(String name)
53   *    {
54   *      _name = name;
55   *    }
56   *
57   *    public String getName()
58   *    {
59   *      return _name;
60   *    }
61   *
62   *    public List getKids()
63   *    {
64   *      return _kids;
65   *    }
66   *
67   *    private final String _name;
68   *    private final List _kids = new ArrayList();
69   * }
70   * </pre>
71   * You can construct a tree by:
72   * <pre>
73   * Person john = new Person("John Smith");
74   * Person kim = new Person("Kim Smith");
75   * Person tom = new Person("Tom Smith");
76   * Person ira = new Person("Ira Wickrememsinghe");
77   * Person mallika = new Person("Mallika Wickremesinghe");
78   *
79   * john.getKids().add(kim);
80   * john.getKids().add(tom);
81   * ira.getKids().add(mallika);
82   *
83   * // create the list of root nodes:
84   * List people = new ArrayList();
85   * people.add(john);
86   * people.add(ira);
87   * </pre>
88   * Now you can construct a MenuModel by:
89   * <pre>
90   * MenuModel model = new ChildPropertyMenuModel(people, "kids", focusRowKey);
91   * </pre>
92   */
93  public class ChildPropertyMenuModel extends MenuModel
94  {
95    /**
96     * Creates a MenuModel
97     * @param instance The Collection of root nodes of this tree.
98     *                 This can be a List or array of beans (or Maps).
99     *                 This instance is first converted into a CollectionModel (see
100    *                 {@link ModelUtils#toCollectionModel}).
101    * @param childProperty This property will be used to get at the child Lists from each bean (or
102    *                      Map). Bean rules will be used to find a getter method that matches this
103    *                      property. If each node is a Map, this property will be passed in to the
104    *                      Map's get method to get the child List.
105    * @param focusRowKey null or the row key in focus
106    */
107   public ChildPropertyMenuModel(Object instance, String childProperty, Object focusRowKey)
108   {
109     this();
110     setChildProperty(childProperty);
111     setWrappedData(instance);
112     this._focusRowKey = focusRowKey;
113   }
114 
115   /**
116    * No-arg constructor for use with managed-beans.
117    * Must call the {@link #setChildProperty} and
118    * {@link #setWrappedData} methods after constructing this instance.
119    */
120   public ChildPropertyMenuModel()
121   {
122     Node root = new Node(null);
123     _path.add(root);
124   }
125 
126   /**
127    * Gets the rowKey of the current row.
128    */
129   @Override
130   public Object getRowKey()
131   {
132     final int sz = _path.size() - 1;
133     Object lastRowkey = _getRowKey();
134     if ((sz == 0) && (lastRowkey == null))
135       return null;  // root collection
136     
137     // have to clone the path here. otherwise, we have to say that
138     // this tree model cannot be mutated while accessing the path
139     // returned by this method.
140     List<Object> path = new ArrayList<Object>(sz+1);
141     if (sz > 0)
142     {
143       for(int i=0; i<sz; i++)
144       {
145         Node node = _getNode(i);
146         path.add(node.childModel.getRowKey());
147       }
148     }
149     path.add(lastRowkey);
150     return path;
151   }
152 
153   /**
154    * Selects a new current row. The row that matches the given rowKey is made current.
155    * @param rowKey use null to access the root collection 
156    */
157   @SuppressWarnings("unchecked")
158   @Override
159   public void setRowKey(Object rowKey)
160   {
161     Node root = _getNode(0);
162     _path.clear();
163     _path.add(root);
164     
165     List<Object> path = (List<Object>) rowKey;
166     if ((path == null) || (path.size() == 0))
167     {
168       setRowIndex(-1);
169       return;
170     }
171       
172     int lastIndex = path.size() - 1;
173     for(int i=0; i<lastIndex; i++)
174     {
175       Object pathKey = path.get(i);
176       _setRowKey(pathKey);
177       enterContainer();
178     }
179     
180     _setRowKey(path.get(lastIndex));
181   }
182 
183   @SuppressWarnings("unchecked")
184   @Override
185   public Object getContainerRowKey(Object childKey)
186   {
187     List<Object> path = (List<Object>) childKey;
188     if ((path == null) || (path.size() <= 1))
189       return null;
190     
191     // wrap sublist in a Serializable copy, since sublist usually returns non-Serializable
192     // instances
193     return CollectionUtils.newSerializableList(path.subList(0, path.size() - 1));
194   }
195 
196   @Override
197   public int getRowCount()
198   {
199     return _getModel().getRowCount();
200   }
201 
202   @Override
203   public Object getRowData()
204   {
205     return _getModel().getRowData();
206   }
207 
208   @Override
209   public boolean isRowAvailable()
210   {
211     return _getModel().isRowAvailable();
212   }
213 
214   @Override
215   public boolean isContainer()
216   {
217     Object rowData = getRowData();
218     Object value = getChildData(rowData);
219     
220     if (value != null)
221     {
222       if (value instanceof Collection<?>)
223       {
224         return !((Collection<?>)value).isEmpty();
225       }
226       else if (value.getClass().isArray())
227       {
228         return Array.getLength(value) > 0;
229       }
230       else if (value instanceof DataModel)
231       {
232         return ((DataModel)value).getRowCount() > 0;
233       }
234     }
235     
236     return value != null;
237   }
238 
239   @Override
240   public void enterContainer()
241   {
242     Object rowData = getRowData();
243     if (rowData == null)
244       throw new IllegalStateException(_LOG.getMessage("NULL_ROWDATA"));
245     Node node = new Node(rowData);
246     _path.add(node);
247   }
248 
249   @Override
250   public void exitContainer()
251   {
252     int sz = _path.size();
253     if (sz > 1)
254       _path.remove(sz - 1);
255     else
256       throw new IllegalStateException(_LOG.getMessage("CANNOT_EXIT_ROOT_CONTAINER"));
257   }
258   
259   /**
260    * Gets the instance being wrapped by this MenuModel.
261    */
262   @Override
263   public Object getWrappedData()
264   {
265     return _wrappedData;
266   }
267 
268   /**
269    * Sets the instance being wrapped by this MenuModel.
270    * Calling this method sets the path to empty.
271    */
272   @Override
273   public void setWrappedData(Object data)
274   {
275     Node root = _getNode(0);
276     root.childModel = ModelUtils.toCollectionModel(data);
277     setRowKey(null);
278     _wrappedData = data;
279   }
280 
281   /**
282    * Gets the property name used to fetch the children.
283    */
284   public final String getChildProperty()
285   {
286     return _childProperty;
287   }
288 
289   /**
290    * Sets the property name used to fetch the children.
291    */
292   public final void setChildProperty(String childProperty)
293   {
294     _childProperty = childProperty;
295   }
296 
297   @Override
298   public int getRowIndex()
299   {
300     return _getModel().getRowIndex();
301   }
302 
303   @Override
304   public void setRowIndex(int rowIndex)
305   {
306     _getModel().setRowIndex(rowIndex);
307   }
308 
309   @Override
310   public boolean isSortable(String property)
311   {
312     return _getModel().isSortable(property);
313   }
314 
315   @Override
316   public List<SortCriterion> getSortCriteria()
317   {
318     return _getModel().getSortCriteria();
319   }
320 
321   @Override
322   public void setSortCriteria(List<SortCriterion> criteria)
323   {
324     _getModel().setSortCriteria(criteria);
325   }
326 
327   /**
328    * Gets the child data for a node. This child data will be converted into a CollectionModel by
329    * calling {@link #createChildModel}.
330    * @param parentData the node to get the child data from
331    * @return the List or array that is the child data.
332    * must return null for a leaf node.
333    */
334   protected Object getChildData(Object parentData)
335   {
336     String prop = getChildProperty();
337     if (prop == null)
338       return null;
339     
340     return SortableModel.__resolveProperty(parentData, prop);
341   }
342 
343   /**
344    * Converts childData into a CollectionModel.
345    * This method calls {@link ModelUtils#toCollectionModel}
346    * @param childData the data to convert. This can be a List or array.
347    */
348   protected CollectionModel createChildModel(Object childData)
349   {
350     CollectionModel model = ModelUtils.toCollectionModel(childData);
351     model.setRowIndex(-1);
352     return model;
353   }
354 
355   private Object _getRowKey()
356   {
357     return _getModel().getRowKey();
358   }
359 
360   private void _setRowKey(Object key)
361   {
362     _getModel().setRowKey(key);
363   }
364   
365   private Node _getCurrentNode()
366   {
367     return _getNode(_path.size() - 1);
368   }
369 
370   private Node _getNode(int index)
371   {
372     return _path.get(index);    
373   }
374 
375   private CollectionModel _getModel()
376   {
377     Node node = _getCurrentNode();
378     CollectionModel model = node.childModel;
379   
380     if (model == null)
381     {
382       Object value = getChildData(node.parentData);
383       model = createChildModel(value);
384       node.childModel = model;
385     }
386     return model;
387   }
388 
389   public Object getFocusRowKey()
390   {
391     return _focusRowKey;
392   }
393 
394   public void setFocusRowKey(Object focusRowKey)
395   {
396     _focusRowKey = focusRowKey;
397   }
398 
399   private static final class Node
400   {
401     public Node(Object parentData)
402     {
403       this.parentData = parentData;
404     }
405 
406     public final Object parentData;
407     CollectionModel childModel = null;
408   }
409 
410   private final List<Node> _path = new ArrayList<Node>(5);
411   private String _childProperty;
412   private Object _wrappedData;
413   private Object _focusRowKey;
414   private static final TrinidadLogger _LOG =
415     TrinidadLogger.createTrinidadLogger(ChildPropertyMenuModel.class);
416 }