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 }