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.component;
20
21 import java.util.AbstractMap;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25
26 import java.util.Map;
27 import java.util.Set;
28
29 import javax.faces.component.UIComponent;
30
31 import javax.faces.component.visit.VisitCallback;
32 import javax.faces.component.visit.VisitContext;
33
34 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
35 import org.apache.myfaces.trinidad.model.CollectionModel;
36 import org.apache.myfaces.trinidad.model.LocalRowKeyIndex;
37 import org.apache.myfaces.trinidad.model.ModelUtils;
38 import org.apache.myfaces.trinidad.model.RowKeySet;
39 import org.apache.myfaces.trinidad.model.TreeLocalRowKeyIndex;
40 import org.apache.myfaces.trinidad.model.TreeModel;
41 import org.apache.myfaces.trinidad.util.StringUtils;
42
43
44 /**
45 * Base class for components that take a TreeModel, which is a hierarchical model.
46 *
47 * @version $Name: $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/component/UIXHierarchy.java#0 $) $Date: 10-nov-2005.19:09:52 $
48 */
49 @JSFComponent
50 public abstract class UIXHierarchy extends UIXCollection implements CollectionComponent, LocalRowKeyIndex,
51 TreeLocalRowKeyIndex
52 {
53 /**
54 * Create a Page component with the given render-type
55 */
56 protected UIXHierarchy(String rendererType)
57 {
58 super(rendererType);
59 }
60
61
62 protected UIXHierarchy()
63 {
64 this(null);
65 }
66
67 @Override
68 public CollectionModel createCollectionModel(CollectionModel current, Object value)
69 {
70 TreeModel model = ModelUtils.toTreeModel(value);
71 model.setRowKey(null);
72 return model;
73 }
74
75 /**
76 * Gets the index of the first visible row in this tree
77 * @return zero-based index. not implemented yet.
78 */
79 // TODO implement this
80 public int getFirst()
81 {
82 return 0;
83 }
84
85 /**
86 * Gets the maximum number of rows that this tree should show at a time.
87 * @return not implemented yet.
88 */
89 // TODO implement this
90 public int getRows()
91 {
92 return 0;
93 }
94
95 /**
96 * Treats the current element as a parent element and steps into the children.
97 * A new path is constructed by appending the null value to the old path.
98 * The rowData becomes null.
99 * It is legal to call this method only if {@link #isContainer}
100 * returns true.
101 * @see TreeModel#enterContainer
102 */
103 public final void enterContainer()
104 {
105 preRowDataChange();
106 getTreeModel().enterContainer();
107 postRowDataChange();
108 }
109
110
111 /**
112 * Changes the rowData to be the parent rowData.
113 * A new path is constructed by removing the last rowKey from the old path.
114 * The element that is identified by the new path is made current.
115 * @see TreeModel#exitContainer
116 */
117 public final void exitContainer()
118 {
119 preRowDataChange();
120 getTreeModel().exitContainer();
121 postRowDataChange();
122 }
123
124 /**
125 * Checks to see if the current element is a container of other elements.
126 * @see TreeModel#isContainer
127 * @return true if the current element contains other elements.
128 */
129 public final boolean isContainer()
130 {
131 return getTreeModel().isContainer();
132 }
133
134 /**
135 * Checks to see if the container is empty.
136 * @see TreeModel#isContainerEmpty
137 * @return true if the current container element has no children.
138 */
139 public boolean isContainerEmpty()
140 {
141 return getTreeModel().isContainerEmpty();
142 }
143
144 /**
145 * Gets the depth of the current row in this tree hierarchy
146 * @see TreeModel#getDepth()
147 * @return zero for any root rows.
148 */
149 public int getDepth()
150 {
151 return getTreeModel().getDepth();
152 }
153
154 /**
155 * Gets the depth of the current row in this tree hierarchy
156 * @see TreeModel#getDepth(Object)
157 * @return zero for any root rows.
158 */
159 public int getDepth(Object rowKey)
160 {
161 return getTreeModel().getDepth(rowKey);
162 }
163
164 /**
165 * Gets the rowKey of the current row's container.
166 * @see TreeModel#getContainerRowKey
167 */
168 public Object getContainerRowKey()
169 {
170 return getTreeModel().getContainerRowKey();
171 }
172
173 /**
174 * Gets the rowKey of the given row's container.
175 * @see TreeModel#getContainerRowKey(Object)
176 */
177 public Object getContainerRowKey(Object childKey)
178 {
179 return getTreeModel().getContainerRowKey(childKey);
180 }
181
182 /**
183 * Gets the all the rowKeys of the ancestors of the given child row.
184 * @see TreeModel#getAllAncestorContainerRowKeys(Object)
185 */
186 public List<Object> getAllAncestorContainerRowKeys(Object childRowKey)
187 {
188 return getTreeModel().getAllAncestorContainerRowKeys(childRowKey);
189 }
190
191 //
192 // TreeLocalRowKeyIndex implementation
193 //
194
195 /**
196 * Indicates whether data for a child model (children of the current node) is
197 * locally available.
198 * @see TreeModel#isChildCollectionLocallyAvailable()
199 * @return true if child data is locally available
200 */
201 public boolean isChildCollectionLocallyAvailable()
202 {
203 return getTreeModel().isChildCollectionLocallyAvailable();
204 }
205
206 /**
207 * Indicates whether child data for the node with the given index is
208 * locally available.
209 * @see TreeModel#isChildCollectionLocallyAvailable(int)
210 * @param index row index to check
211 * @return true if child data is available, false otherwise
212 */
213 public boolean isChildCollectionLocallyAvailable(int index)
214 {
215 return getTreeModel().isChildCollectionLocallyAvailable(index);
216 }
217
218 /**
219 * Indicates whether child data for the node with the given row key is
220 * locally available.
221 * @see TreeModel#isChildCollectionLocallyAvailable(Object)
222 * @param rowKey row key to check
223 * @return true if child data is available, false otherwise
224 */
225 public boolean isChildCollectionLocallyAvailable(Object rowKey)
226 {
227 return getTreeModel().isChildCollectionLocallyAvailable(rowKey);
228 }
229
230 /**
231 * Check if a range of rows is locally available starting from a row index. The range
232 * can include child nodes in any expanded nodes within the range.
233 * @param startIndex staring index for the range
234 * @param rowCount number of rows in the range
235 * @param disclosedRowKeys set of expanded nodes which may fall within the range to check for
236 * availability
237 * @return <code>true</code> if range of rows is locally available <code>flase</code> otherwise
238 * @see TreeModel#areRowsLocallyAvailable(int, int, RowKeySet)
239 */
240 public boolean areRowsLocallyAvailable(int startIndex, int rowCount,
241 RowKeySet disclosedRowKeys)
242 {
243 return getTreeModel().areRowsLocallyAvailable(startIndex, rowCount, disclosedRowKeys);
244 }
245
246 /**
247 * Check if a range of rows is locally available starting from a row key. The range
248 * can include child nodes in any expanded nodes within the range.
249 * @param startRowKey staring row key for the range
250 * @param rowCount number of rows in the range
251 * @param disclosedRowKeys set of expanded nodes which may fall within the range to check for
252 * availability
253 * @return <code>true</code> if range of rows is locally available <code>flase</code> otherwise
254 * @see TreeModel#areRowsLocallyAvailable(Object, int, RowKeySet)
255 */
256 public boolean areRowsLocallyAvailable(Object startRowKey, int rowCount,
257 RowKeySet disclosedRowKeys)
258 {
259 return getTreeModel().areRowsLocallyAvailable(startRowKey, rowCount, disclosedRowKeys);
260 }
261
262 /**
263 * Check if a range of rows is locally available starting from current position. The range
264 * can include child nodes in any expanded nodes within the range.
265 * @param rowCount number of rows in the range
266 * @param disclosedRowKeys set of expanded nodes which may fall within the range to check for
267 * availability
268 * @return <code>true</code> if range of rows is locally available <code>flase</code> otherwise
269 * @see TreeModel#areRowsLocallyAvailable(int , RowKeySet)
270 */
271 public boolean areRowsLocallyAvailable(int rowCount,
272 RowKeySet disclosedRowKeys)
273 {
274 return getTreeModel().areRowsLocallyAvailable(rowCount, disclosedRowKeys);
275 }
276
277
278 /**
279 * Enhances the varStatusMap created by the super class to include:<ul>
280 * <li>"hierarchicalIndex" - returns an array containing the row indices of heirarchy of the currrent row, for e.g. [0,1,2]
281 * This attribute is expensive to compute because of moving currency to calculate the row index for
282 * each parent collection in the tree hierarchy.
283 * </li>
284 * <li>"hierarchicalLabel" - returns a string label representing the hierarchy of that row, for e.g. 1.1, 1.1.1.
285 * The labels are 1 based vs 0 based for rowIndex.
286 * This attribute is expensive to compute because of moving currency to calculate the row index for
287 * each parent collection in the tree hierarchy.
288 * </li>
289 * </ul>
290 */
291 @Override
292 protected Map<String, Object> createVarStatusMap()
293 {
294 final Map<String, Object> map = super.createVarStatusMap();
295 return new AbstractMap<String, Object>()
296 {
297 @Override
298 public Object get(Object key)
299 {
300 if("hierarchicalIndex".equals(key))
301 {
302 return _getHierarchicalIndex().toArray();
303 }
304 if("hierarchicalLabel".equals(key))
305 {
306 List<Integer> rowIndices = _getHierarchicalIndex();
307 if(rowIndices.size() == 0)
308 return "";
309
310 Integer[] indexArray = new Integer[rowIndices.size()];
311
312 for(int i = 0; i < rowIndices.size(); i++)
313 {
314 indexArray[i] = Integer.valueOf(rowIndices.get(i).intValue()+1);
315 }
316 return StringUtils.join(indexArray, '.');
317 }
318 return map.get(key);
319 }
320
321 /**
322 * Returns an array of row indexes for the hierarchy of that row
323 */
324 private List<Integer> _getHierarchicalIndex()
325 {
326 Object rowKey = getRowKey();
327 if(rowKey == null)
328 return Collections.emptyList();
329
330 TreeModel treeModel = getTreeModel();
331 List<Integer> rowIndices = new ArrayList<Integer>();
332
333 // Use model APIs for moving currency(setRowIndex/Key) vs component API to avoid performance issue
334 // associated with stamp state saving
335 try
336 {
337 rowIndices.add(treeModel.getRowIndex());
338
339 Object childRowKey = treeModel.getContainerRowKey(rowKey);
340 while(childRowKey != null)
341 {
342 treeModel.setRowKey(childRowKey);
343 rowIndices.add(treeModel.getRowIndex());
344 childRowKey = treeModel.getContainerRowKey(childRowKey);
345 }
346 Collections.reverse(rowIndices);
347 }
348 finally
349 {
350 // make sure that we restore our currency to the original state
351 treeModel.setRowKey(rowKey);
352 }
353 return rowIndices;
354 }
355
356 @Override
357 public Set<Map.Entry<String, Object>> entrySet()
358 {
359 return map.entrySet();
360 }
361 };
362 }
363
364 /**
365 * Gets the TreeModel that this tree is displaying.
366 */
367 protected final TreeModel getTreeModel()
368 {
369 TreeModel model = (TreeModel) getCollectionModel();
370 return model;
371 }
372
373 @Override
374 protected List<UIComponent> getStamps()
375 {
376 UIComponent nodeStamp = getFacet("nodeStamp");
377 if (nodeStamp != null)
378 return Collections.singletonList(nodeStamp);
379 else
380 return Collections.emptyList();
381 }
382
383 abstract public Object getFocusRowKey();
384
385 protected final boolean visitLevel(
386 VisitContext visitContext,
387 VisitCallback callback,
388 List<UIComponent> stamps)
389 {
390 if (getRowCount() != 0)
391 {
392 if (!stamps.isEmpty())
393 {
394 int oldRow = getRowIndex();
395 int first = getFirst();
396 int last = TableUtils.getLast(this, first);
397
398 try
399 {
400 for (int i = first; i <= last; i++)
401 {
402 setRowIndex(i);
403
404 for (UIComponent currStamp : stamps)
405 {
406 // visit the stamps. If we have visited all of the visit targets then return early
407 if (UIXComponent.visitTree(visitContext, currStamp, callback))
408 return true;
409 }
410 }
411 }
412 finally
413 {
414 setRowIndex(oldRow);
415 }
416 }
417 }
418
419 return false;
420 }
421
422 protected final boolean visitHierarchy(
423 VisitContext visitContext,
424 VisitCallback callback,
425 List<UIComponent> stamps,
426 RowKeySet disclosedRowKeys)
427 {
428 int oldRow = getRowIndex();
429 int first = getFirst();
430 int last = TableUtils.getLast(this, first);
431
432 try
433 {
434 for(int i = first; i <= last; i++)
435 {
436 setRowIndex(i);
437 if (!stamps.isEmpty())
438 {
439 for (UIComponent currStamp : stamps)
440 {
441 // visit the stamps. If we have visited all of the visit targets then return early
442 if (UIXComponent.visitTree(visitContext, currStamp, callback))
443 return true;
444 }
445 }
446
447 if (isContainer() && ((disclosedRowKeys == null) || disclosedRowKeys.isContained()))
448 {
449 enterContainer();
450
451 try
452 {
453 // visit this container. If we have visited all of the visit targets then return early
454 if (visitHierarchy(visitContext, callback, stamps, disclosedRowKeys))
455 return true;
456 }
457 finally
458 {
459 exitContainer();
460 }
461 }
462 }
463 }
464 finally
465 {
466 setRowIndex(oldRow);
467 }
468
469 return false;
470 }
471 }