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 }