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