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 /**
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:
428 return new HashSet<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>) ((HashSet<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 }