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.util.Arrays;
22 import java.util.Collections;
23 import java.util.List;
24
25
26 /**
27 * The data model used by Trinidad Tree components. A TreeModel is
28 * responsible for understanding how to iterate through an
29 * object graph, enter and leave containers, and identify
30 * rows of objects within each container. Within any one
31 * container, a TreeModel looks just like a CollectionModel,
32 * which extends the JSF DataModel class. (So, to understand
33 * this class, start by learning how DataModel works).
34 * <p>
35 * <h3>Entering and exiting containers</h3>
36 * <p>
37 * TreeModel extends CollectionModel to add support for container rows,
38 * which are entered and exited with enterContainer() and exitContainer()
39 * methods. Within a container, row indices (get/setRowIndex())
40 * are relative to the container. However, row keys - get/setRowKey(),
41 * from the CollectionModel API - are always global for the entire
42 * tree model, so it is sufficient to call setRowKey() to enter and
43 * exit all the needed parents.
44 * <p>
45 * <h3>Lazy loading of contents</h3>
46 * <p>
47 * When a tree or treeTable iterates through the model,
48 * it will generally seek to see if a given node is a
49 * container - with the <code>isContainer()</code> method -
50 * and also see if the node is empty (and therefore
51 * not expandable) with the <code>isContainerEmpty()</code>
52 * method. The default implementation of that latter
53 * method involves entering the child and seeing how
54 * many children it has. As a result, by default,
55 * you will see one more level of content being
56 * requested than is actually visible on screen. To
57 * avoid this, provide a custom override of <code>
58 * isContainerEmpty()</code> to return a value
59 * without actually entering the container. It
60 * is acceptable for this method to return a "false negative" -
61 * to return false when there might actually not be any
62 * contents - if that is the most efficient approach possible.
63 * <p>
64 * The <code>ChildPropertyTreeModel</code> class is a useful
65 * basic subclass, but largely requires that you have the
66 * entire object model fully loaded. If you require
67 * lazy loading, you'll likely need a custom implementation.
68 * <p>
69 * <h3>Further documentation</h3>
70 * <p>
71 * Rows in the TreeModel may (recursively) contain other rows.
72 * To figure out if the current row is a container, call the
73 * {@link #isContainer} method.
74 * If a row is a container, use the {@link #enterContainer} method
75 * to access its child rows. Once the {@link #enterContainer} method is called
76 * all the CollectionModel API's methods (like {@link #getRowCount})
77 * operate on the child collection.
78 * To return back to the parent row, use the {@link #exitContainer} method.
79 * <P>
80 * Given the following tree structure:
81 * <pre>
82 * |-Root1 (rowKey="r1", rowIndex=0)
83 * | |-Folder1 (rowKey="r1f1", rowIndex=0)
84 * | | |-Node1 (rowKey="r1f1n1", rowIndex=0)
85 * | | |-Node2 (rowKey="r1f1n2", rowIndex=1)
86 * | | |-Node3 (rowKey="r1f1n3", rowIndex=2)
87 * | |
88 * | |-Folder2 (rowKey="r1f2", rowIndex=1)
89 * | |-Node4 (rowKey="r1f2n1", rowIndex=0)
90 * |
91 * |-Root2 (rowKey="r2", rowIndex=1)
92 * |-Root3 (rowKey="r3", rowIndex=2)
93 * |-Root4 (rowKey="r4", rowIndex=3)
94 * </pre>
95 * To point the tree to the root collection call:
96 * <code>setRowKey(null)</code>.<br>
97 * Now, <code>getRowCount()</code> returns 4.<br>
98 * <code>setRowIndex(1);getRowData()</code> returns <code>Root2</code>.<br>
99 * <code>setRowKey("r4");getRowData()</code> returns <code>Root4</code>.
100 * <P>
101 * To access <code>Node4</code> use:
102 * <pre>
103 * setRowIndex(0); // isContainer()==true
104 * enterContainer(); // enter Root1, getRowCount()==2
105 * setRowIndex(1); // isContainer()==true
106 * enterContainer(); // enter Folder2, getRowCount()==1
107 * setRowIndex(0); // isContainer()==false
108 * getRowData();
109 * </pre>
110 * Or, more simply:
111 * <pre>
112 * setRowKey("r1f2n1");
113 * getRowData();
114 * </pre>
115 * At this point, to get at <code>Node3</code> use:
116 * <pre>
117 * exitContainer(); // exit Folder2, Root1 is now the current row.
118 * setRowIndex(0);
119 * enterContainer(); // enter Folder1, getRowCount()==3
120 * setRowIndex(2);
121 * getRowData();
122 * </pre>
123 * Or, more simply:
124 * <pre>
125 * setRowKey("r1f1n3");
126 * getRowData();
127 * </pre>
128 */
129 public abstract class TreeModel extends CollectionModel implements TreeLocalRowKeyIndex
130 {
131
132 /**
133 * Tests to see if the row identified by getRowData() is a container element.
134 * Use {@link #isContainerEmpty} to see if the current container element actually
135 * has children, or is an empty container.
136 * @return true if the current element may contain children.
137 */
138 public abstract boolean isContainer();
139
140 /**
141 * Tests to see if the current container element actually has children.
142 * This could be more efficient than calling
143 * {@link #enterContainer} followed by {@link #getRowCount}.
144 * This method is permitted to return false even if the container is actually
145 * empty.
146 * This method should only be called if {@link #isContainer} returns true.
147 * @return true if the current container element has no children. If there
148 * is any doubt as to whether or not the container has children, this method
149 * should return false.
150 */
151 public boolean isContainerEmpty()
152 {
153 if (!isContainer())
154 return true;
155
156 enterContainer();
157 try
158 {
159 int kids = getRowCount();
160 if (kids < 0)
161 {
162 setRowIndex(0);
163 return !isRowAvailable();
164 }
165 return (kids == 0);
166 }
167 finally
168 {
169 exitContainer();
170 }
171 }
172
173 /**
174 * This Collection changes to reflect the children of the current rowData,
175 * and the current rowData changes to be null.
176 * The current rowIndex becomes -1. This method should only be called
177 * if {@link #isContainer()} returns true.
178 * {@link #getRowCount} can be used to get the number of children.
179 */
180 public abstract void enterContainer();
181
182 /**
183 * Pops back up to the parent collection.
184 * The current rowData becomes the rowData of the parent.
185 * This Collection will change to include the children of the new rowData.
186 */
187 public abstract void exitContainer();
188
189 /**
190 * Gets the rowKey of the current row's container row.
191 * This implementation calls {@link #getContainerRowKey(Object)} with
192 * the current rowKey.
193 */
194 public final Object getContainerRowKey()
195 {
196 Object key = getRowKey();
197 Object parentKey = getContainerRowKey(key);
198 return parentKey;
199 }
200
201 /**
202 * Gets the rowkey of each container, starting from the top most
203 * container, down to the container of the given child rowKey.
204 * The root container (which always has the null rowKey) is not included in
205 * this list. The given childRowKey is not included in this list.
206 * <p>
207 * Given the following tree structure:
208 * <pre>
209 * |-Root1 (rowKey="r1")
210 * | |-Folder1 (rowKey="r1f1")
211 * | | |-Node1 (rowKey="r1f1n1")
212 * </pre>
213 * Calling <code>getAllAncestorContainerRowKeys("r1f1n1")</code>
214 * returns a List of two items:"r1" and "r1f1", in that order.
215 *
216 * @param childRowKey identifies the child row.
217 * @return An empty list is returned if the child row is a root row and
218 * has no parent containers. Each item in this list is a rowKey
219 * and is of type {@link Object}.
220 * The first rowKey (in this list) is the top most container. The last
221 * rowKey is the immediate container of the given childRowKey.
222 */
223 public List<Object> getAllAncestorContainerRowKeys(Object childRowKey)
224 {
225 if (childRowKey == null)
226 return Collections.emptyList();
227
228 int size = getDepth(childRowKey);
229 if (size <= 0)
230 return Collections.emptyList();
231
232 Object[] keys = new Object[size];
233 for(int i=size-1; i>=0; i--)
234 {
235 childRowKey = getContainerRowKey(childRowKey);
236 assert childRowKey != null;
237 keys[i] = childRowKey;
238 }
239 return Collections.unmodifiableList(Arrays.asList(keys));
240 }
241
242 /**
243 * Gets the rowKey of a given child row's container row.
244 * <pre>
245 * |-Root1 (rowKey="r1", containerRowKey=null)
246 * | |-Folder1 (rowKey="r1f1", containerRowKey="r1")
247 * | | |-Node1 (rowKey="r1f1n1", containerRowKey="r1f1")
248 * | | |-Node2 (rowKey="r1f1n2", containerRowKey="r1f1")
249 * </pre>
250 * @param childRowKey the rowKey of the child row.
251 * @return the rowKey of the container, or null if the child is a root row.
252 */
253 public abstract Object getContainerRowKey(Object childRowKey);
254
255 /**
256 * Gets the depth of the current row within this tree hierarchy.
257 * <br>
258 * This implementation simply calls {@link #getDepth(Object)} with
259 * the current rowKey.
260 */
261 public final int getDepth()
262 {
263 Object key = getRowKey();
264 return getDepth(key);
265 }
266
267 /**
268 * Gets the depth of the given row within the tree hierarchy.
269 * The depth is a measure of how far the given row is from its top-level
270 * container row.
271 * Root-level rows have a depth of zero. All the immediate children of each
272 * root row have a depth of one.
273 * <pre>
274 * |-Root1 (depth=0)
275 * | |-Folder1 (depth=1)
276 * | | |-Node1 (depth=2)
277 * | | |-Node2 (depth=2)
278 * | | |-Node3 (depth=2)
279 * | |-Folder2 (depth=1)
280 * |-Root2 (depth=0)
281 * </pre>
282 */
283 public int getDepth(Object rowKey)
284 {
285 Object key = rowKey;
286 int depth = 0;
287 while(true)
288 {
289 key = getContainerRowKey(key);
290 if (key == null)
291 break;
292 depth++;
293 }
294 return depth;
295 }
296
297
298 //
299 // Below is the default implemenation for the TreeLocalRowKeyIndex interface.
300 //
301
302 /**
303 * Indicates whether data for a child model (children of the current node) is
304 * locally available. Locally available means no data fetch is required
305 * as a result of a call to {@link #enterContainer}.
306 * @return The default implementation returns <code>false</code>
307 * <p>
308 * Override this method if the TreeModel implementation supports caching of nodes.
309 * If caching is supported, the implementation should also return a caching strategy from
310 * <code>CollectionModel.getCachingStrategy()</code>
311 */
312 public boolean isChildCollectionLocallyAvailable()
313 {
314 return false;
315 }
316
317 /**
318 * Indicates whether child data for the node with the given index is
319 * locally available. This method first checks to see if the parent node
320 * at the given index is locally available by calling {@link #isRowLocallyAvailable(int}.
321 * If the parent node is locally available, this method moves the model to the
322 * parent node and calls {@link #isChildCollectionLocallyAvailable()}
323 * The current row does not change after this call
324 * @param index
325 * @return true if child data is available, false otherwise
326 */
327 public boolean isChildCollectionLocallyAvailable(int index)
328 {
329 if (isRowLocallyAvailable(index))
330 {
331 int oldIndex = getRowIndex();
332 try
333 {
334 setRowIndex(index);
335 return isChildCollectionLocallyAvailable();
336 }
337 finally
338 {
339 setRowIndex(oldIndex);
340 }
341 }
342 else
343 {
344 return false;
345 }
346 }
347
348 /**
349 * Indicates whether child data for the node with the given row key is
350 * locally available.
351 * <p>
352 * This method first checks to see if the parent node
353 * with the given row key is locally available by calling {@link #isRowLocallyAvailable(Object)}.
354 * If the parent node is locally available, this method moves the model to the
355 * parent node and calls {@link #isChildCollectionLocallyAvailable()}
356 * The current row does not change after this call
357 * @param rowKey
358 * @return true if child data is available, false otherwise
359 */
360 public boolean isChildCollectionLocallyAvailable(Object rowKey)
361 {
362 if (isRowLocallyAvailable(rowKey))
363 {
364 Object oldKey = getRowKey();
365 try
366 {
367 setRowKey(rowKey);
368 return isChildCollectionLocallyAvailable();
369 }
370 finally
371 {
372 setRowKey(oldKey);
373 }
374 }
375 else
376 {
377 return false;
378 }
379 }
380
381 /**
382 * Check if a range of rows is locally available starting from a row index. The range
383 * can include child nodes in any expanded nodes within the range.
384 * <p>
385 * This implementation checks the row at startIndex for availability and, if
386 * available, moves the model to startIndex and calls
387 * <code>areRowsLocallyAvailable(rowCount, disclosedRowKeys)</code>.
388 * The current row does not change after this call
389 * @param startIndex staring index for the range
390 * @param rowCount number of rows in the range
391 * @return true if range of rows is locally available false otherwise
392 */
393 public boolean areRowsLocallyAvailable(int startIndex, int rowCount, RowKeySet disclosedRowKeys)
394 {
395
396 boolean available = false;
397 if (isRowLocallyAvailable(startIndex))
398 {
399 Object oldKey = getRowKey();
400 try
401 {
402 setRowIndex(startIndex);
403 available = areRowsLocallyAvailable(rowCount, disclosedRowKeys);
404 }
405 finally
406 {
407 setRowKey(oldKey);
408 }
409 }
410 return available;
411 }
412
413 /**
414 * Check if a range of rows is locally available starting from a row key. The range
415 * can include child nodes in any expanded nodes within the range.
416 * <p>
417 * This implementation checks the row at startRowKey for availability and, if
418 * available, moves the model to startRowKey and calls
419 * <code>areRowsLocallyAvailable(rowCount, disclosedRowKeys)</code>.
420 * The current row does not change after this call
421 * @param startRowKey staring row key for the range
422 * @param rowCount number of rows in the range
423 * @param disclosedRowKeys set of expanded nodes which may fall within the range to check for
424 * @return true if range of rows is locally available false otherwise
425 */
426 public boolean areRowsLocallyAvailable(Object startRowKey, int rowCount, RowKeySet disclosedRowKeys)
427 {
428 boolean available = false;
429 if (isRowLocallyAvailable(startRowKey))
430 {
431 Object oldKey = getRowKey();
432 try
433 {
434 setRowKey(startRowKey);
435 available = areRowsLocallyAvailable(rowCount, disclosedRowKeys);
436 }
437 finally
438 {
439 setRowKey(oldKey);
440 }
441 }
442 return available;
443 }
444
445 /**
446 * Check if a range of rows is locally available starting from current position. The range
447 * can include child nodes in any expanded nodes within the range.
448 * This implementation walks locally available nodes in the current collection and drills into any expanded child
449 * collections. The node traversal can continue to the siblings of the current node.
450 * Node traversal ends when a node or a child collection is not locally available or
451 * when rowCount nodes are visited or the last root node is reached.
452 * The current row does not change after this call.
453 * @param rowCount number of rows in the range
454 * @param disclosedRowKeys set of expanded nodes which may fall within the range to check for
455 * @return true if range of rows is locally available false otherwise
456 */
457 public boolean areRowsLocallyAvailable(int rowCount, RowKeySet disclosedRowKeys)
458 {
459
460 boolean available = false;
461 Object startingRowKey = getRowKey();
462
463 if (startingRowKey != null)
464 {
465 available = _areRowsLocallyAvailable(startingRowKey, rowCount, disclosedRowKeys);
466 }
467 return available;
468 }
469
470
471 /**
472 * Check if a total of "count" rows are locally available starting from a "rowKey". Take into account
473 * child rows in expanded nodes within the range
474 * @param rowKey starting row key to check
475 * @param count row count to check
476 * @param disclosedRows set of expanded nodes
477 * @return true if count rows are locally available false otherwise
478 */
479 private boolean _areRowsLocallyAvailable(Object rowKey, int count, RowKeySet disclosedRows)
480 {
481 if (!isRowLocallyAvailable(rowKey))
482 return false;
483
484 Object oldKey = getRowKey();
485 try
486 {
487 setRowKey(rowKey);
488 int startIndex = getRowIndex();
489 // start from the current node and walk up the parent hierarchy if necessary
490 while ((count = _walkAvailableNodes(startIndex, count, disclosedRows)) > 0 && getDepth() > 0)
491 {
492 exitContainer();
493 startIndex = getRowIndex();
494 startIndex += 1;
495 }
496 return count >= 0;
497 }
498 finally
499 {
500 setRowKey(oldKey);
501 }
502 }
503
504 /**
505 * Walk available child nodes starting from a row index in the current collection
506 * and consume "count" nodes. Drill into expanded nodes.
507 * @param startIndex starting child index within the current collection
508 * @param count number of nodes to check
509 * @param disclosedRows set of expanded nodes
510 * @return -1 if rows are not available; 0 if "count" rows are available; > 0
511 * if the requested count is greater than child row count for the current
512 * collection and all rows up to child row count are locally available
513 */
514 private int _walkAvailableNodes(int startIndex, int count, RowKeySet disclosedRows)
515 {
516 int index = startIndex;
517 int rowCount = getRowCount();
518
519 if (rowCount < 0)
520 return -1;
521
522 while (index < rowCount && count > 0)
523 {
524 if (!isRowLocallyAvailable(index))
525 return -1;
526
527 --count;
528 setRowIndex(index);
529 Object key = getRowKey();
530 if (disclosedRows.contains(key))
531 {
532 if (isChildCollectionLocallyAvailable())
533 {
534 enterContainer();
535 setRowIndex(0);
536 count = _walkAvailableNodes(0, count, disclosedRows);
537 if (count < 0)
538 return -1;
539 exitContainer();
540 }
541 else
542 {
543 // children of expanded node are not available
544 return -1;
545 }
546 }
547 ++index;
548 }
549
550 return count;
551 }
552 }