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