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