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