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 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;
136
137
138
139
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
192
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 }