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.io.Serializable;
22  
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map.Entry;
30  import java.util.NoSuchElementException;
31  import java.util.Set;
32  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
33  
34  /**
35   * Implements a collection of rowKeys from a TreeModel.
36   * The methods on this class are optimized such that it is possible
37   * to add/remove all the rowkeys in a subtree in constant time.
38   * <P>
39   * The generic type E is the type of a rowKey.
40   */
41  public class RowKeySetTreeImpl extends RowKeySet implements Serializable 
42  {
43    /**
44     * Creates a new Set that is initially empty.
45     */
46    public RowKeySetTreeImpl()
47    {
48      this(false);
49    }
50    
51    /**
52     * Creates a new Set, that may contain every rowKey by default.
53     * @param addAll if this is true, every rowKey is initially added to this set.
54     */
55    public RowKeySetTreeImpl(boolean addAll)
56    {
57      _root = new Node<Object>(addAll);
58    }
59      
60    /**
61     * Tests to see if the given rowKey is included in this Set.
62     * @return true If the rowKey is included in this Set.
63     */
64    @Override
65    public boolean contains(Object rowKey)
66    {
67      return _isContained(rowKey);
68    }
69    
70    /**
71     * @deprecated do not use. this will be removed post Tier 1.
72     */
73    @Override
74    @Deprecated
75    public boolean isContainedByDefault()
76    {
77      TreeModel model = getCollectionModel();
78      Object rowkey = model.getRowKey();
79      return new Search().find(rowkey).isDefaultContained;
80    }
81  
82    @Override
83    public Iterator<Object> iterator()
84    {
85      return new PathIterator();
86    }
87      
88    /**
89     * Adds the given rowKey to this Set.
90     * @return false if the given rowKey was already in this Set.
91     * @see #remove(Object)
92     * @see #addAll()
93     */
94    @Override
95    public boolean add(Object rowKey)
96    {
97      return _setContained(rowKey, true);    
98    }
99    
100   /**
101    * Removes the given rowKey from this Set.
102    * @return false if the given rowKey was already not in this Set.
103    * @see #add
104    * @see #removeAll()
105    */
106   @Override
107   public boolean remove(Object rowKey)
108   {
109     return _setContained(rowKey, false);    
110   }
111 
112   /**
113    * Adds the current rowKey and all rowKeys beneath the current rowKey to this Set.
114    * This method executes in constant time.
115    * @see #add
116    * @see #removeAll()
117    */
118   @Override
119   public void addAll()
120   {
121     _selectAll(true);
122   }
123 
124   /**
125    * Removes the current rowKey and all rowKeys beneath the current rowKey to this Set.
126    * This method executes in constant time.
127    * @see #remove(Object)
128    * @see #clear()
129    * @see #addAll()
130    */
131   @Override
132   public void removeAll()
133   {
134     _selectAll(false);
135   }
136 
137   /**
138    * {@inheritDoc}
139    * <P>
140    * If the parameter is another RowKeySetTreeImpl, this method is
141    * optimized to give superior performance and avoid iteration.
142    */
143   @Override
144   public boolean addAll(Collection<? extends Object> other)
145   {
146     if (other instanceof RowKeySetTreeImpl)
147     {
148       RowKeySetTreeImpl otherset = (RowKeySetTreeImpl) other;
149       return _processOperation(this._root, otherset._root, true);
150     }
151     return super.addAll(other);
152   }
153 
154   /**
155    * {@inheritDoc}
156    * <P>
157    * If the parameter is another RowKeySetTreeImpl, this method is
158    * optimized to give superior performance and avoid iteration.
159    */
160   @Override
161   public boolean removeAll(Collection<?> other)
162   {
163     if (other instanceof RowKeySetTreeImpl)
164     {
165       RowKeySetTreeImpl otherset = (RowKeySetTreeImpl) other;
166       return _processOperation(this._root, otherset._root, false);
167     }
168     return super.removeAll(other);
169   }
170 
171   private boolean _processOperation(Node<Object> set1, Node<Object> set2, boolean add)
172   {
173     /*      
174      * setXdef = setX.isDefaultContained
175      * setXdif = setX.isDifferent
176      * asterisks (*) indicate changes.
177      * 
178      * TABLE ---------------------------------------------------
179      |-----------Inputs---------|--------Outputs---------------|
180      | set1def | set2def | add  | removeAll | addAll | set1def |
181      |    0    |    0    |  1   |     0     |   1    |    0    |
182      |    0    |    1    |  1   |     1     |   1    |    1*   |
183      |    1    |    0    |  1   |     0     |   0    |    1    |
184      |    1    |    1    |  1   |     1     |   0    |    1    |
185      |    0    |    0    |  0   |     0     |   0    |    0    |
186      |    0    |    1    |  0   |     1     |   0    |    0    |
187      |    1    |    0    |  0   |     0     |   1    |    1    |
188      |    1    |    1    |  0   |     1     |   1    |    0*   |
189      |---------------------------------------------------------|
190      */
191   
192     boolean hasChanges = false;
193 
194     // See TABLE (above) 'removeAll' column:
195     // if set2 contains everything, then there is no point hanging on to
196     // any set1-deltas that are not in set2, so remove them:
197     if (set2.isDefaultContained && set1.keySet().retainAll(set2.keySet()))
198         hasChanges = true;
199 
200     // See TABLE (above) 'addAll' column:
201     // this "addAll" flag controls whether to process any set2-deltas that are not
202     // already in set1. If set1 has everything by default and we're adding set2,
203     // then there is no point processing any set2-deltas not already in set1.
204     // Similarly, if set1 has nothing by default and we're removing set2,
205     // then there is no point processing any set2-deltas not already in set1.
206     // So only process the set2-deltas if we're doing an add (and set1
207     // does not contain everything) or we're doing a remove (and set1 
208     // has everything):
209     boolean addAll = add ^ set1.isDefaultContained;
210     
211     for(Entry<Object, Node<Object>> en:set2.entrySet())
212     {
213       Object segment = en.getKey();
214       Node<Object> subset2 = en.getValue();
215       Node<Object> subset1 = set1.get(segment);
216       
217       if (subset1 == null)
218       {
219         if (addAll)
220         {
221           subset1 = new Node<Object>(set1, segment);
222           hasChanges = true;
223         }
224         else
225           continue;
226       }
227       if (_processOperation(subset1, subset2, add))
228         hasChanges = true;
229     }
230 
231     // See TABLE (above) 'Outputs/set1Def' column:
232     // if set2 contains everything by default, then that will affect
233     // the default flag of set1:
234     if (set2.isDefaultContained && (set1.isDefaultContained != add))
235     {
236       set1.isDefaultContained = add;
237       // since we toggled the default state, toggle the diff state
238       // as well so that we maintain the status for this node:
239       set1.isDifferent = !set1.isDifferent;
240       hasChanges = true;
241     }
242 
243     // if this node is contained by set2, then depending on the
244     // add flag, this node should (not) be contained by set1:
245     if ((set2.isDefaultContained ^ set2.isDifferent) &&
246         ((set1.isDefaultContained ^ set1.isDifferent) != add))
247     {
248       set1.isDifferent = !set1.isDifferent;
249       hasChanges = true;
250     }
251 
252     return hasChanges;
253   }
254 
255   /**
256    * Removes all rowKeys from this Set.
257    * This method executes in the same time as
258    * {@link HashMap#clear()}
259    */
260   @Override
261   public void clear()
262   {
263     _root.clear();
264     _root.isDefaultContained = _root.isDifferent = false;
265   }
266 
267   /**
268    * Gets the number of elements contained by this set.
269    * Does not force the underlying model to compute its size. 
270    * @return -1 if the number of elements is unknown.
271    */
272   @Override
273   public int getSize()
274   {
275     return _getSize(null, _root, getCollectionModel(), false);
276   }
277 
278   /**
279    * Gets the number of elements in this Set.
280    * This might force the underlying model to compute its size.
281    * @return a non-negative number.
282    */
283   @Override
284   public int size()
285   {
286     return _getSize(null, _root, getCollectionModel(), true);
287   }
288 
289   @Override
290   public boolean isEmpty() 
291   {
292     return (getSize() == 0);
293   }
294 
295   /**
296    * Sets the TreeModel associated with this Set.
297    * @param model This must be of type {@link TreeModel}
298    */
299   @Override
300   public final void setCollectionModel(CollectionModel model)
301   {
302     if (!(model instanceof TreeModel))
303       throw new IllegalArgumentException();
304 
305     _model = (TreeModel) model;
306   }
307 
308   /**
309    * Creates a clone of this Set. RowKeys may be added/removed from the
310    * clone without affecting this instance.
311    */
312   @Override
313   public RowKeySetTreeImpl clone()
314   {
315     RowKeySetTreeImpl clone = (RowKeySetTreeImpl) super.clone();
316     clone._root = _root.clone();
317     return clone;
318   }
319 
320   /**
321    * @deprecated not implemented.
322    */
323   @Deprecated
324   @Override
325   public void invertAll()
326   {
327     // TODO
328     throw new UnsupportedOperationException();
329   }
330 
331   /**
332    * Gets the TreeModel associated with this set.
333    * This TreeModel will be used to get the current rowKey, and also to
334    * get parent rowKeys, from child rowKeys.
335    * @see TreeModel#getRowKey
336    */
337   @Override
338   protected TreeModel getCollectionModel()
339   {
340     return _model;
341   }
342 
343   /**
344    * Gets the total number of nodes in the subtree of the given TreeModel.
345    * 
346    * WARNING: this method changes the TreeModel's currency.
347    * The caller is responsible for restoring the model currency.
348    * 
349    * @param exclusions any rowKeys present in this Set are excluded from the count.
350    */
351   @SuppressWarnings("unchecked")
352   private int _getTreeSize(TreeModel model, Set<Object> exclusions)
353   {
354     int sz = 0;
355     for(int i=0;true;i++)
356     {
357       model.setRowIndex(i);
358       if (model.isRowAvailable())
359       {
360         Object rowkey = model.getRowKey();
361         if (exclusions.contains(rowkey))
362           continue;
363         sz++;
364         if (model.isContainer())
365         {
366           model.enterContainer();
367           Set<Object> empty = Collections.emptySet();
368           sz += _getTreeSize(model, empty);
369           model.exitContainer();
370         }
371       }
372       else
373         return sz;
374     }
375   }
376 
377   private int _getSize(Object rowkey, Node<Object> set, TreeModel model,  boolean fetchall)
378   {
379     // special-case the root collection:
380     int sz = ((rowkey != null) && (set.isDefaultContained ^ set.isDifferent)) ? 1 : 0;
381     if (set.isDefaultContained)
382     {
383       if (!fetchall)
384         return -1;
385         
386       Object old = model.getRowKey();
387       try
388       {
389         model.setRowKey(rowkey);
390         // special-case the root collection:
391         if (rowkey == null)
392         {
393           sz += _getTreeSize(model, set.keySet());
394         }
395         else if (model.isContainer())
396         {
397           model.enterContainer();
398           sz += _getTreeSize(model, set.keySet());
399         }
400       }
401       finally
402       {
403         model.setRowKey(old);
404       }
405     }
406     
407     for(Entry<Object, Node<Object>> en:set.entrySet())
408     {
409       Object newrowkey = en.getKey();
410       Node<Object> subset = en.getValue();
411       int size = _getSize(newrowkey, subset, model, fetchall);
412       if (size < 0)
413         return -1;
414       sz+= size;
415     }
416     return sz;
417   }
418 
419   /**
420    * adds or removes all the paths rooted at the current path 
421    * @param isSelectAll if true does an add-all. else does remove-all.
422    */
423   private void _selectAll(final boolean isSelectAll)
424   {
425     Search search = new Search()
426     {
427       @Override
428       protected boolean create(Node<Object> parent, Object rowkey)
429       {
430         // if the parent does not have the correct default, then
431         // we need to add entries for the children, since we need
432         // to store a delta:
433         return (parent.isDefaultContained != isSelectAll);
434       }
435       
436       @Override
437       protected Node<Object> found(Node<Object> child)
438       {
439         child.isDefaultContained = isSelectAll;
440         child.isDifferent = false;
441         child.clear();
442         return null;
443       }
444     };
445 
446     TreeModel model = getCollectionModel();
447     Object rowkey = model.getRowKey();
448     search.find(rowkey);    
449   }
450 
451   private boolean _isContained(Object rowkey)
452   {
453     Search search = new Search()
454     {
455       @Override
456       protected Node<Object> notFound(Node<Object> parent, Object rowkey)
457       {
458         return parent.isDefaultContained ? parent : null;
459       }
460       
461       @Override
462       protected Node<Object> found(Node<Object> child)
463       {
464         return (child.isDefaultContained ^ child.isDifferent) ? child : null;
465       }
466     };
467     
468     return (search.find(rowkey) != null);
469   }
470   
471   /**
472    * Adds or removes the given path from this set.
473    * @param isContained If true, the current path is added. Otherwise,
474    * it is removed.
475    * @return true if this Set changed due to this operation. 
476    */
477   private boolean _setContained(Object rowkey, final boolean isContained)
478   {
479     Search search = new Search()
480     {
481       @Override
482       protected boolean create(Node<Object> parent, Object rowkey)
483       {
484         // only need to create child deltas, if the parent's
485         // default is wrong:
486         return parent.isDefaultContained != isContained;
487       }
488       
489       @Override
490       protected Node<Object> notFound(Node<Object> parent, Object rowkey)
491       {
492         return null;
493       }
494     };
495 
496     Node<Object> current = search.find(rowkey);
497     if ((current != null) &&
498         ((current.isDefaultContained ^ current.isDifferent) != isContained))
499     {
500       current.isDifferent = !current.isDifferent;
501       return true;
502     }
503     return false;
504   }
505 
506   /**
507    * Advances the currency of the given TreeModel to the next node in a
508    * depth-first walk of the tree.
509    * @param minDepth the minimum depth of the rowkey. use this to
510    * walk within a subtree. Use 0 to walk entire tree.
511    * @param recurseChildren if true, will walk children.
512    * @return true if the currency of the model was successfully advanced to
513    * the next rowData.
514    */
515   private static boolean _advanceToNextItem(
516     TreeModel model, int minDepth, boolean recurseChildren)
517   {
518     assert minDepth >= 0;
519     
520     if (recurseChildren && model.isRowAvailable() && model.isContainer())
521     {
522       model.enterContainer();
523       model.setRowIndex(-1);
524     }
525     while(true)
526     {
527       int ri = model.getRowIndex();
528       model.setRowIndex(ri+1);
529       if (model.isRowAvailable())
530         return true;
531         
532       int depth = model.getDepth();
533       if (depth <= minDepth)
534         return false;
535         
536       model.exitContainer();
537     }
538   }
539 
540   // Needs to be Serializable and Cloneable - but HashMap already is
541   private static final class Node<K> extends HashMap<K, Node<K>>
542      /* implements Serializable, Cloneable */
543   {
544     public boolean isDifferent = false;
545     public boolean isDefaultContained = false;
546 
547     public Node(boolean isDefaultContained)
548     {
549       this.isDefaultContained = isDefaultContained;
550     }
551     
552     public Node(Node<K> parent, K segment)
553     {
554       this(parent.isDefaultContained);
555       parent.put(segment, this);
556     }
557     
558     // clone all the values as well:
559     private void _deepClone(Node<K> root)
560     {
561       for(Entry<K, Node<K>> en:root.entrySet())
562       {
563         Node<K> original = en.getValue();
564         Node<K> clone = original.clone();
565         en.setValue(clone);
566       }
567     }
568     
569     @SuppressWarnings("unchecked")
570     @Override
571     public Node<K> clone()
572     {
573       Node<K> clone = (Node<K>) super.clone();
574       _deepClone(clone);
575       return clone;
576     }
577 
578     private static final long serialVersionUID = 1L;
579   }
580 
581   private class Search
582   {
583     public Search()
584     {
585     }
586     
587     protected boolean create(Node<Object> parent, Object rowkey)
588     {
589       return false;
590     }
591 
592     protected Node<Object> notFound(Node<Object> parent, Object rowkey)
593     {
594       return parent;
595     }
596 
597     protected Node<Object> found(Node<Object> result)
598     {
599       return result;
600     }
601 
602     public Node<Object> find(Object rowkey)
603     {
604       Node<Object> current = _root;
605       if (rowkey != null)
606       {
607         TreeModel model = getCollectionModel();
608         List<Object> parentkeys = model.getAllAncestorContainerRowKeys(rowkey);
609         List<Object> allkeys = new ArrayList<Object>(parentkeys.size() + 1);
610         allkeys.addAll(parentkeys);
611         allkeys.add(rowkey);
612         for(Object key:allkeys)
613         {
614           Node<Object> next = current.get(key);
615           if (next == null)
616           {
617             if (create(current, key))
618               next = new Node<Object>(current, key);       
619             else
620               return notFound(current, key);
621           }
622           current = next;
623         }
624       }
625       return found(current);
626     }
627   }
628 
629   private final class PathIterator implements Iterator<Object>
630   {
631     PathIterator()
632     {
633       _value = _next(); // initialize;
634     }
635 
636     public Object next()
637     {
638       if (!hasNext())
639         throw new NoSuchElementException();
640       Object value = _value;
641       _value = _next();
642       return value;
643     }
644     
645     public boolean hasNext()
646     {
647       return (_value != null);
648     }
649     
650     public void remove()
651     {
652       throw new UnsupportedOperationException();
653     }
654     
655     private boolean _containsSubtree(Object rowkey)
656     {
657       Search search = new Search()
658       {
659         @Override
660         protected Node<Object> notFound(Node<Object> parent, Object rowkey)
661         {
662           return parent.isDefaultContained ? parent : null;
663         }
664       };
665       Node<Object> current = search.find(rowkey);
666       return (current != null) && 
667         ((!current.isEmpty()) || current.isDefaultContained);
668     }
669     
670     private Object _next()
671     {
672       TreeModel model = getCollectionModel();
673       if (model == null)
674         return null;
675 
676       Object oldPath = model.getRowKey();
677       try 
678       {
679         model.setRowKey(_currPath);
680         while(true)
681         {
682           boolean searchChildren = _containsSubtree(_currPath);
683           boolean hasMore = _advanceToNextItem(model, 0, searchChildren);
684           if (!hasMore)
685             return null;
686 
687           _currPath = model.getRowKey();
688           if (contains(_currPath))
689             return _currPath;
690         }
691       } finally 
692       {
693         model.setRowKey(oldPath);
694       }
695     }
696     
697     private Object _value;
698     private Object _currPath = null;
699   }
700   
701   private Node<Object> _root;
702   private transient TreeModel _model = null;
703   private static final long serialVersionUID = 1L;
704   private static final TrinidadLogger _LOG =
705     TrinidadLogger.createTrinidadLogger(RowKeySetTreeImpl.class);
706 }