summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java
blob: 6f368f6a1cc1252db655bbcdc05e2eba641b2da1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
/* 
@VaadinApache2LicenseForJavaFiles@
 */

package com.vaadin.data.util;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.data.Property;

/**
 * <p>
 * A wrapper class for adding external ordering to containers not implementing
 * the {@link com.vaadin.data.Container.Ordered} interface.
 * </p>
 * 
 * <p>
 * If the wrapped container is changed directly (that is, not through the
 * wrapper), and does not implement Container.ItemSetChangeNotifier and/or
 * Container.PropertySetChangeNotifier the hierarchy information must be updated
 * with the {@link #updateOrderWrapper()} method.
 * </p>
 * 
 * @author Vaadin Ltd.
 * @since 3.0
 */
@SuppressWarnings("serial")
public class ContainerOrderedWrapper implements Container.Ordered,
        Container.ItemSetChangeNotifier, Container.PropertySetChangeNotifier {

    /**
     * The wrapped container
     */
    private final Container container;

    /**
     * Ordering information, ie. the mapping from Item ID to the next item ID
     */
    private Hashtable<Object, Object> next;

    /**
     * Reverse ordering information for convenience and performance reasons.
     */
    private Hashtable<Object, Object> prev;

    /**
     * ID of the first Item in the container.
     */
    private Object first;

    /**
     * ID of the last Item in the container.
     */
    private Object last;

    /**
     * Is the wrapped container ordered by itself, ie. does it implement the
     * Container.Ordered interface by itself? If it does, this class will use
     * the methods of the underlying container directly.
     */
    private boolean ordered = false;

    /**
     * The last known size of the wrapped container. Used to check whether items
     * have been added or removed to the wrapped container, when the wrapped
     * container does not send ItemSetChangeEvents.
     */
    private int lastKnownSize = -1;

    /**
     * Constructs a new ordered wrapper for an existing Container. Works even if
     * the to-be-wrapped container already implements the Container.Ordered
     * interface.
     * 
     * @param toBeWrapped
     *            the container whose contents need to be ordered.
     */
    public ContainerOrderedWrapper(Container toBeWrapped) {

        container = toBeWrapped;
        ordered = container instanceof Container.Ordered;

        // Checks arguments
        if (container == null) {
            throw new NullPointerException("Null can not be wrapped");
        }

        // Creates initial order if needed
        updateOrderWrapper();
    }

    /**
     * Removes the specified Item from the wrapper's internal hierarchy
     * structure.
     * <p>
     * Note : The Item is not removed from the underlying Container.
     * </p>
     * 
     * @param id
     *            the ID of the Item to be removed from the ordering.
     */
    private void removeFromOrderWrapper(Object id) {
        if (id != null) {
            final Object pid = prev.get(id);
            final Object nid = next.get(id);
            if (first.equals(id)) {
                first = nid;
            }
            if (last.equals(id)) {
                first = pid;
            }
            if (nid != null) {
                prev.put(nid, pid);
            }
            if (pid != null) {
                next.put(pid, nid);
            }
            next.remove(id);
            prev.remove(id);
        }
    }

    /**
     * Registers the specified Item to the last position in the wrapper's
     * internal ordering. The underlying container is not modified.
     * 
     * @param id
     *            the ID of the Item to be added to the ordering.
     */
    private void addToOrderWrapper(Object id) {

        // Adds the if to tail
        if (last != null) {
            next.put(last, id);
            prev.put(id, last);
            last = id;
        } else {
            first = last = id;
        }
    }

    /**
     * Registers the specified Item after the specified itemId in the wrapper's
     * internal ordering. The underlying container is not modified. Given item
     * id must be in the container, or must be null.
     * 
     * @param id
     *            the ID of the Item to be added to the ordering.
     * @param previousItemId
     *            the Id of the previous item.
     */
    private void addToOrderWrapper(Object id, Object previousItemId) {

        if (last == previousItemId || last == null) {
            addToOrderWrapper(id);
        } else {
            if (previousItemId == null) {
                next.put(id, first);
                prev.put(first, id);
                first = id;
            } else {
                prev.put(id, previousItemId);
                next.put(id, next.get(previousItemId));
                prev.put(next.get(previousItemId), id);
                next.put(previousItemId, id);
            }
        }
    }

    /**
     * Updates the wrapper's internal ordering information to include all Items
     * in the underlying container.
     * <p>
     * Note : If the contents of the wrapped container change without the
     * wrapper's knowledge, this method needs to be called to update the
     * ordering information of the Items.
     * </p>
     */
    public void updateOrderWrapper() {

        if (!ordered) {

            final Collection<?> ids = container.getItemIds();

            // Recreates ordering if some parts of it are missing
            if (next == null || first == null || last == null || prev != null) {
                first = null;
                last = null;
                next = new Hashtable<Object, Object>();
                prev = new Hashtable<Object, Object>();
            }

            // Filter out all the missing items
            final LinkedList<?> l = new LinkedList<Object>(next.keySet());
            for (final Iterator<?> i = l.iterator(); i.hasNext();) {
                final Object id = i.next();
                if (!container.containsId(id)) {
                    removeFromOrderWrapper(id);
                }
            }

            // Adds missing items
            for (final Iterator<?> i = ids.iterator(); i.hasNext();) {
                final Object id = i.next();
                if (!next.containsKey(id)) {
                    addToOrderWrapper(id);
                }
            }
        }
    }

    /*
     * Gets the first item stored in the ordered container Don't add a JavaDoc
     * comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public Object firstItemId() {
        if (ordered) {
            return ((Container.Ordered) container).firstItemId();
        }
        return first;
    }

    /*
     * Tests if the given item is the first item in the container Don't add a
     * JavaDoc comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public boolean isFirstId(Object itemId) {
        if (ordered) {
            return ((Container.Ordered) container).isFirstId(itemId);
        }
        return first != null && first.equals(itemId);
    }

    /*
     * Tests if the given item is the last item in the container Don't add a
     * JavaDoc comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public boolean isLastId(Object itemId) {
        if (ordered) {
            return ((Container.Ordered) container).isLastId(itemId);
        }
        return last != null && last.equals(itemId);
    }

    /*
     * Gets the last item stored in the ordered container Don't add a JavaDoc
     * comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public Object lastItemId() {
        if (ordered) {
            return ((Container.Ordered) container).lastItemId();
        }
        return last;
    }

    /*
     * Gets the item that is next from the specified item. Don't add a JavaDoc
     * comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public Object nextItemId(Object itemId) {
        if (ordered) {
            return ((Container.Ordered) container).nextItemId(itemId);
        }
        if (itemId == null) {
            return null;
        }
        return next.get(itemId);
    }

    /*
     * Gets the item that is previous from the specified item. Don't add a
     * JavaDoc comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public Object prevItemId(Object itemId) {
        if (ordered) {
            return ((Container.Ordered) container).prevItemId(itemId);
        }
        if (itemId == null) {
            return null;
        }
        return prev.get(itemId);
    }

    /**
     * Registers a new Property to all Items in the Container.
     * 
     * @param propertyId
     *            the ID of the new Property.
     * @param type
     *            the Data type of the new Property.
     * @param defaultValue
     *            the value all created Properties are initialized to.
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     */
    @Override
    public boolean addContainerProperty(Object propertyId, Class<?> type,
            Object defaultValue) throws UnsupportedOperationException {

        return container.addContainerProperty(propertyId, type, defaultValue);
    }

    /**
     * Creates a new Item into the Container, assigns it an automatic ID, and
     * adds it to the ordering.
     * 
     * @return the autogenerated ID of the new Item or <code>null</code> if the
     *         operation failed
     * @throws UnsupportedOperationException
     *             if the addItem is not supported.
     */
    @Override
    public Object addItem() throws UnsupportedOperationException {

        final Object id = container.addItem();
        if (!ordered && id != null) {
            addToOrderWrapper(id);
        }
        return id;
    }

    /**
     * Registers a new Item by its ID to the underlying container and to the
     * ordering.
     * 
     * @param itemId
     *            the ID of the Item to be created.
     * @return the added Item or <code>null</code> if the operation failed
     * @throws UnsupportedOperationException
     *             if the addItem is not supported.
     */
    @Override
    public Item addItem(Object itemId) throws UnsupportedOperationException {
        final Item item = container.addItem(itemId);
        if (!ordered && item != null) {
            addToOrderWrapper(itemId);
        }
        return item;
    }

    /**
     * Removes all items from the underlying container and from the ordering.
     * 
     * @return <code>true</code> if the operation succeeded, otherwise
     *         <code>false</code>
     * @throws UnsupportedOperationException
     *             if the removeAllItems is not supported.
     */
    @Override
    public boolean removeAllItems() throws UnsupportedOperationException {
        final boolean success = container.removeAllItems();
        if (!ordered && success) {
            first = last = null;
            next.clear();
            prev.clear();
        }
        return success;
    }

    /**
     * Removes an Item specified by the itemId from the underlying container and
     * from the ordering.
     * 
     * @param itemId
     *            the ID of the Item to be removed.
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     * @throws UnsupportedOperationException
     *             if the removeItem is not supported.
     */
    @Override
    public boolean removeItem(Object itemId)
            throws UnsupportedOperationException {

        final boolean success = container.removeItem(itemId);
        if (!ordered && success) {
            removeFromOrderWrapper(itemId);
        }
        return success;
    }

    /**
     * Removes the specified Property from the underlying container and from the
     * ordering.
     * <p>
     * Note : The Property will be removed from all the Items in the Container.
     * </p>
     * 
     * @param propertyId
     *            the ID of the Property to remove.
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     * @throws UnsupportedOperationException
     *             if the removeContainerProperty is not supported.
     */
    @Override
    public boolean removeContainerProperty(Object propertyId)
            throws UnsupportedOperationException {
        return container.removeContainerProperty(propertyId);
    }

    /*
     * Does the container contain the specified Item? Don't add a JavaDoc
     * comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public boolean containsId(Object itemId) {
        return container.containsId(itemId);
    }

    /*
     * Gets the specified Item from the container. Don't add a JavaDoc comment
     * here, we use the default documentation from implemented interface.
     */
    @Override
    public Item getItem(Object itemId) {
        return container.getItem(itemId);
    }

    /*
     * Gets the ID's of all Items stored in the Container Don't add a JavaDoc
     * comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public Collection<?> getItemIds() {
        return container.getItemIds();
    }

    /*
     * Gets the Property identified by the given itemId and propertyId from the
     * Container Don't add a JavaDoc comment here, we use the default
     * documentation from implemented interface.
     */
    @Override
    public Property<?> getContainerProperty(Object itemId, Object propertyId) {
        return container.getContainerProperty(itemId, propertyId);
    }

    /*
     * Gets the ID's of all Properties stored in the Container Don't add a
     * JavaDoc comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public Collection<?> getContainerPropertyIds() {
        return container.getContainerPropertyIds();
    }

    /*
     * Gets the data type of all Properties identified by the given Property ID.
     * Don't add a JavaDoc comment here, we use the default documentation from
     * implemented interface.
     */
    @Override
    public Class<?> getType(Object propertyId) {
        return container.getType(propertyId);
    }

    /*
     * Gets the number of Items in the Container. Don't add a JavaDoc comment
     * here, we use the default documentation from implemented interface.
     */
    @Override
    public int size() {
        int newSize = container.size();
        if (lastKnownSize != -1 && newSize != lastKnownSize
                && !(container instanceof Container.ItemSetChangeNotifier)) {
            // Update the internal cache when the size of the container changes
            // and the container is incapable of sending ItemSetChangeEvents
            updateOrderWrapper();
        }
        lastKnownSize = newSize;
        return newSize;
    }

    /*
     * Registers a new Item set change listener for this Container. Don't add a
     * JavaDoc comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public void addListener(Container.ItemSetChangeListener listener) {
        if (container instanceof Container.ItemSetChangeNotifier) {
            ((Container.ItemSetChangeNotifier) container)
                    .addListener(new PiggybackListener(listener));
        }
    }

    /*
     * Removes a Item set change listener from the object. Don't add a JavaDoc
     * comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public void removeListener(Container.ItemSetChangeListener listener) {
        if (container instanceof Container.ItemSetChangeNotifier) {
            ((Container.ItemSetChangeNotifier) container)
                    .removeListener(new PiggybackListener(listener));
        }
    }

    /*
     * Registers a new Property set change listener for this Container. Don't
     * add a JavaDoc comment here, we use the default documentation from
     * implemented interface.
     */
    @Override
    public void addListener(Container.PropertySetChangeListener listener) {
        if (container instanceof Container.PropertySetChangeNotifier) {
            ((Container.PropertySetChangeNotifier) container)
                    .addListener(new PiggybackListener(listener));
        }
    }

    /*
     * Removes a Property set change listener from the object. Don't add a
     * JavaDoc comment here, we use the default documentation from implemented
     * interface.
     */
    @Override
    public void removeListener(Container.PropertySetChangeListener listener) {
        if (container instanceof Container.PropertySetChangeNotifier) {
            ((Container.PropertySetChangeNotifier) container)
                    .removeListener(new PiggybackListener(listener));
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
     * java.lang.Object)
     */
    @Override
    public Item addItemAfter(Object previousItemId, Object newItemId)
            throws UnsupportedOperationException {

        // If the previous item is not in the container, fail
        if (previousItemId != null && !containsId(previousItemId)) {
            return null;
        }

        // Adds the item to container
        final Item item = container.addItem(newItemId);

        // Puts the new item to its correct place
        if (!ordered && item != null) {
            addToOrderWrapper(newItemId, previousItemId);
        }

        return item;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
     */
    @Override
    public Object addItemAfter(Object previousItemId)
            throws UnsupportedOperationException {

        // If the previous item is not in the container, fail
        if (previousItemId != null && !containsId(previousItemId)) {
            return null;
        }

        // Adds the item to container
        final Object id = container.addItem();

        // Puts the new item to its correct place
        if (!ordered && id != null) {
            addToOrderWrapper(id, previousItemId);
        }

        return id;
    }

    /**
     * This listener 'piggybacks' on the real listener in order to update the
     * wrapper when needed. It proxies equals() and hashCode() to the real
     * listener so that the correct listener gets removed.
     * 
     */
    private class PiggybackListener implements
            Container.PropertySetChangeListener,
            Container.ItemSetChangeListener {

        Object listener;

        public PiggybackListener(Object realListener) {
            listener = realListener;
        }

        @Override
        public void containerItemSetChange(ItemSetChangeEvent event) {
            updateOrderWrapper();
            ((Container.ItemSetChangeListener) listener)
                    .containerItemSetChange(event);

        }

        @Override
        public void containerPropertySetChange(PropertySetChangeEvent event) {
            updateOrderWrapper();
            ((Container.PropertySetChangeListener) listener)
                    .containerPropertySetChange(event);

        }

        @Override
        public boolean equals(Object obj) {
            return obj == listener || (obj != null && obj.equals(listener));
        }

        @Override
        public int hashCode() {
            return listener.hashCode();
        }

    }

}