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.Externalizable;
22  import java.io.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.NoSuchElementException;
31  import java.util.Set;
32  
33  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
34  
35  /**
36   * Implements a set of rowKeys. This set is connected with a CollectionModel
37   * and is actually a subset of the rowKeys contained by the CollectionModel.
38   * This set is used to group together rows that have a common UI property.
39   * For example, all the rows that are currently selected by a user might be
40   * placed in a single RowKeySet.
41   * <p>
42   * This class has very efficient implementations for addAll, clear and
43   * invertAll.
44   */
45  public final class RowKeySetImpl extends RowKeySet implements Externalizable
46  {
47    /**
48     * Creates an initially empty RowKeySet.
49     */
50    public RowKeySetImpl()
51    {
52      this(false);
53    }
54  
55    /**
56     * Creates a new RowKeySet.
57     * @param addAll whether to add every rowKey to this set.
58     */
59    public RowKeySetImpl(boolean addAll)
60    {
61      _default = addAll;
62      _set = Collections.emptySet();
63      _model = null;
64    }
65  
66    /**
67     * Checks to see the current rowKey is contained by this set.
68     * @return true if this set contains the current rowKey
69     */
70    @Override
71    public boolean contains(Object rowKey)
72    {
73      return _isSelected(rowKey);
74    }
75  
76    /**
77     * Adds the current rowKey to this set.
78     * @return true if this set changed
79     */
80    @Override
81    public boolean add(Object rowKey)
82    {
83      return _setSelected(rowKey, true);
84    }
85  
86    /**
87     * Removes the current rowKey from this set.
88     * @return true if this set changed
89     */
90    @Override
91    public boolean remove(Object rowKey)
92    {
93      return _setSelected(rowKey, false);
94    }
95  
96    /**
97     * Adds the current rowKey to this set if it doesn't already exist, removes
98     * it otherwise.
99     * @return true if the row is now added. false otherwise.
100    */
101   @Override
102   public boolean invert(Object rowKey)
103   {
104     Set<Object> set = _getSet(true);
105     if (!set.add(rowKey))
106     {
107       set.remove(rowKey);
108       return _default;
109     }
110     else
111     {
112       return !_default;
113     }
114   }
115 
116   /**
117    * Inverts this set.
118    * All of the contents of this set will be removed. Then rowKeys that were
119    * not previously in this set will be added.
120    * This method executes in constant time.
121    */
122   @Override
123   public void invertAll()
124   {
125     _default = !_default;
126   }
127 
128   /**
129    * Adds every rowKey to this set.
130    * This method executes in constant time.
131    */
132   @Override
133   public void addAll()
134   {
135     _selectAll(true);
136   }
137 
138   @Override
139   public boolean isContainedByDefault()
140   {
141     return _default;
142   }
143 
144   /**
145    * Removes every rowKey from this set.
146    * This method executes in constant time.
147    */
148   @Override
149   public void clear()
150   {
151     _selectAll(false);
152   }
153 
154   @Override
155   public boolean removeAll(Collection<?> c)
156   {
157     if (c instanceof RowKeySetImpl)
158     {
159       RowKeySetImpl other = (RowKeySetImpl) c;
160       if (other._default)
161       {
162         // the other Set has all keys added by default. It will be too
163         // costly to use the default removeAll implementation.
164         // use an optimized one here:
165         return _processAll(other, false);
166       }
167     }
168     return super.removeAll(c);
169   }
170 
171   @Override
172   public boolean addAll(Collection<? extends Object> c)
173   {
174     if (c instanceof RowKeySetImpl)
175     {
176       RowKeySetImpl other = (RowKeySetImpl) c;
177       if (other._default)
178       {
179         // the other Set has all keys added by default. It will be too
180         // costly to use the default addAll implementation.
181         // use an optimized one here:
182         return _processAll(other, true);
183       }
184     }
185     return super.addAll(c);
186   }
187 
188   private boolean _processAll(RowKeySetImpl other, boolean addAll)
189   {
190     Set<Object> set = _getSet(false);
191     Set<Object> otherSet = other._getSet(false);
192     if (_default == addAll)
193     {
194       // This Set already uses the correct default state. So all we have to do
195       // is make sure the Set-of-deltas on this Set is correctly synchronized with
196       // the Set-of-deltas on the other Set:
197 
198       /* There are two cases that fall into this group:
199          1) A.addAll(B) where
200               A = {all except X,Y,Z}
201               B = {all except W,X,Y}
202             result: A = {all except X,Y}
203 
204          2) A.removeAll(B) where
205               A = {X,Y,Z}
206               B = {all except W,X,Y}
207             result: A = {X,Y}
208       */
209       return set.retainAll(otherSet);
210     }
211     else
212     {
213       /* There are two cases that fall into this group:
214          3) A.addAll(B) where
215               A = {X,Y,Z}
216               B = {all except W,X,Y}
217             result: A = {all except W}
218 
219          4) A.removeAll(B) where
220               A = {all except X,Y,Z}
221               B = {all except W,X,Y}
222             result: A = {W}
223       */
224 
225       // Make sure this Set uses the correct default state:
226       _default = addAll;
227       // and then the set-of-deltas on the other Set become
228       // the set-of-deltas for this Set; so clone it so that we can reuse it:
229       otherSet = _clone(otherSet);
230       // however, we need to synchronize the two sets-of-deltas:
231       otherSet.removeAll(set);
232       _set = otherSet;
233       return true;
234     }
235   }
236 
237   /**
238    * Changes the underlying CollectionModel being used by this set.
239    * The current rowKey (that is used by some of the methods in this class)
240    * is obtained from this CollectionModel.
241    * <P>
242    * Users typically do not need to call this method.
243    * This method is called by component writers who need to set the models
244    * used by their components on this set.
245    */
246   @Override
247   public final void setCollectionModel(CollectionModel model)
248   {
249     _model = model;
250     if (model == null)
251       _LOG.warning("COLLECTIONMODEL_SET_NULL");
252   }
253 
254   /**
255    * Gets the number of rowKeys in this set (if known).
256    * @return -1 if the number of rowKeys is unknown.
257    */
258   @Override
259   public int getSize()
260   {
261     return _getSize(false);
262   }
263 
264   @Override
265   public int size()
266   {
267     return _getSize(true);
268   }
269 
270   @Override
271   public boolean isEmpty()
272   {
273     return (getSize() == 0);
274   }
275 
276   /**
277    * Gets an iteration of all the rowKeys contained in this Set.
278    * @return each entry is a rowKey.
279    * The CollectionModel and this Set should not be mutated while the
280    * iterator is being used.
281    */
282    @Override
283   public Iterator<Object> iterator()
284    {
285      return _default ? _getNotInSetRowKeyIterator() : _getInSetRowKeyIterator();
286    }
287 
288   /**
289    * Gets the number of rowKeys in this set.
290    * @param fetchAll if true, this method will exhaustively figure out
291    * the number of rowKeys in this set, even when the total number of
292    * rowKeys is not known by the underlying CollectionModel.
293    * @return the total number of rowKeys in this set. If fetchAll is false,
294    * this might return -1 if the number of rowKeys is not known.
295    */
296   private int _getSize(boolean fetchAll)
297   {
298     int setSize = _getSet(false).size();
299     if (_default)
300     {
301       CollectionModel model = getCollectionModel();
302       // if the default is to addAll then we need to subtract the setSize from
303       // the total size:
304       int total = model.getRowCount();
305       if (total < 0)
306       {
307         if (fetchAll)
308         {
309           // should we cache the return value?
310           // I don't think so because once the size is known the underlying
311           // CollectionModel should cache it. If we try to cache it here, then
312           // we won't know when to update it.
313           total = ModelUtils.getRowCount(model);
314         }
315         else
316           return -1;
317       }
318       return total - setSize;
319     }
320     return setSize;
321   }
322 
323   /**
324    * Sets whether or not the given rowKey is added to this set.
325    * @param isSelected true if the item is to be added
326    * @param rowKey the rowKey of the item.
327    * @return true if this set changed
328    */
329   private boolean _setSelected(Object rowKey, boolean isSelected)
330   {
331     if (isSelected == _default)
332     {
333       if (!_set.isEmpty()) // _set != Collections.emptySet()
334       {
335         return _set.remove(rowKey);
336       }
337       return false;
338     }
339     else
340     {
341       return _getSet(true).add(rowKey);
342     }
343   }
344 
345   @SuppressWarnings("unchecked")
346   private Iterator<Object> _getNotInSetRowKeyIterator()
347   {
348     CollectionModel table = getCollectionModel();
349     final Iterator<Object> rowKeyIterator = ModelUtils.getRowKeyIterator(table);
350     final Set<Object> set = _getSet(false);
351     Iterator<Object> iter = new Iterator<Object>()
352     {
353       public Object next()
354       {
355         if (!hasNext())
356           throw new NoSuchElementException();
357         _current = _next;
358         _next = _next();
359         _first = false;
360         return _current;
361       }
362 
363       public void remove()
364       {
365         if (_current == null)
366           throw new IllegalStateException(_LOG.getMessage(
367             "NO_ELEMENT_TO_REMOVE"));
368         Set<Object> mutable = _getSet(true);
369         // since this is the not-in-set iterator, we "remove" the element
370         // by adding it to the Set:
371         mutable.add(_current);
372         _current = null;
373       }
374 
375       public boolean hasNext()
376       {
377         return (_next != null || _first);
378       }
379 
380       private Object _next()
381       {
382         while(rowKeyIterator.hasNext())
383         {
384           Object rowKey = rowKeyIterator.next();
385           if (!set.contains(rowKey))
386             return rowKey;
387         }
388         return null;
389       }
390 
391       private boolean _first = true;
392       private Object _next = null;
393       private Object _current = null;
394     };
395 
396     iter.next(); // initialize;
397     return iter;
398   }
399 
400   private Iterator<Object> _getInSetRowKeyIterator()
401   {
402     return _getSet(false).iterator();
403   }
404 
405   private void _selectAll(boolean isSelected)
406   {
407     _default = isSelected;
408     _set = Collections.emptySet();
409   }
410 
411   private Set<Object> _getSet(boolean create)
412   {
413     if (create && (_set == Collections.emptySet()))
414     {
415       _set = _createSet(10);
416     }
417     return _set;
418   }
419 
420   private Set<Object> _createSet(int sz)
421   {
422     // must be cloneable:
423     return new HashSet<Object>(sz);
424   }
425 
426   private boolean _isSelected(Object rowKey)
427   {
428     Set<Object> set = _getSet(false);
429     boolean isInSet = set.contains(rowKey);
430     return isInSet ^ _default;
431   }
432 
433   // see java.io.Externalizable
434   public void writeExternal(ObjectOutput out) throws IOException
435   {
436     out.writeBoolean(_default);
437     Set<Object> set = _getSet(false);
438     int sz = set.size();
439     out.writeInt(sz);
440     Iterator<Object> iter = set.iterator();
441     for(int i=0; i<sz; i++)
442     {
443       out.writeObject(iter.next());
444     }
445   }
446 
447   // see java.io.Externalizable
448   public void readExternal(ObjectInput in)
449     throws IOException, ClassNotFoundException
450   {
451     _default = in.readBoolean();
452     int sz = in.readInt();
453     if (sz>0)
454     {
455       _set = _createSet(sz);
456       for(int i=0; i<sz; i++)
457       {
458         _set.add(in.readObject());
459       }
460     }
461     else
462       _set = Collections.emptySet();
463   }
464 
465   /**
466    * Creates a shallow clone of this RowKeySet.
467    * Keys may be added or removed from the clone without affecting
468    * this instance.
469    */
470   @Override
471   public RowKeySetImpl clone()
472   {
473     RowKeySetImpl clone = (RowKeySetImpl) super.clone();
474     Set<Object> set = _getSet(false);
475     clone._set = _clone(set);
476     return clone;
477   }
478 
479   /**
480    * Returns a clone of the given Set.
481    * The clone is mutable only if the given Set is not empty.
482    * If the other Set is empty, then the clone is immutable
483    * (although the remove, removeAll and retainAll) methods will still work.
484    */
485   @SuppressWarnings("unchecked")
486   private <T> Set<T> _clone(Set<T> other)
487   {
488     if (other.isEmpty())
489       return Collections.emptySet();
490     else
491       return (Set<T>) ((HashSet<T>) other).clone();
492   }
493 
494   /**
495    * Gets the CollectionModel associated with this set.
496    * The current rowKey (that is used by some of the methods in this class)
497    * is obtained from this CollectionModel.
498    */
499   @Override
500   protected CollectionModel getCollectionModel()
501   {
502     // This code used to contain an assertion that the collection model
503     // was non-null - but a null collection model is a perfectly
504     // legitimate state.  Users of a row key set might want to assert
505     // the collection model is non-null.
506     return _model;
507   }
508 
509 
510   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(RowKeySetImpl.class);
511 
512   private boolean _default;
513   private Set<Object> _set;
514   private transient CollectionModel _model;
515   private static final long serialVersionUID = 1L;
516 }