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