1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
135
136
137
138
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
190
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 }