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