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.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 }