Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.impl / src / main / java / org / gvsig / fmap / dal / feature / impl / DefaultFeatureReferenceSelection.java @ 47421

History | View | Annotate | Download (20.6 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2020 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.dal.feature.impl;
25

    
26
import java.util.Collections;
27
import java.util.HashSet;
28
import java.util.Iterator;
29
import java.util.Set;
30
import org.apache.commons.lang3.builder.ToStringBuilder;
31
import org.gvsig.fmap.dal.DataStore;
32
import org.gvsig.fmap.dal.DataStoreNotification;
33
import org.gvsig.fmap.dal.exception.DataException;
34
import org.gvsig.fmap.dal.feature.FeatureReference;
35
import org.gvsig.fmap.dal.feature.FeatureReferenceSelection;
36
import org.gvsig.fmap.dal.feature.FeatureReferenceIterator;
37
import org.gvsig.fmap.dal.feature.FeatureStore;
38
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
39
import org.gvsig.fmap.dal.feature.FeatureType;
40
import org.gvsig.fmap.dal.feature.impl.undo.FeatureCommandsStack;
41
import org.gvsig.tools.ToolsLocator;
42
import org.gvsig.tools.dispose.impl.AbstractDisposable;
43
import org.gvsig.tools.dynobject.DynStruct;
44
import org.gvsig.tools.exception.BaseException;
45
import org.gvsig.tools.lang.Cloneable;
46
import org.gvsig.tools.observer.Observable;
47
import org.gvsig.tools.observer.Observer;
48
import org.gvsig.tools.observer.impl.DelegateWeakReferencingObservable;
49
import org.gvsig.tools.persistence.PersistentState;
50
import org.gvsig.tools.persistence.exception.PersistenceException;
51
import org.gvsig.tools.visitor.Visitor;
52
import org.slf4j.Logger;
53
import org.slf4j.LoggerFactory;
54

    
55
/**
56
 * Default implementation of a FeatureReferenceSelection, based on the usage of
57
 * a java.util.Set to store individual selected or not selected
58
 * FeatureReferences, depending on the usage of the {@link #reverse()} method.
59
 *
60
 * @author gvSIG Team
61
 */
62
@SuppressWarnings("UseSpecificCatch")
63
public class DefaultFeatureReferenceSelection extends AbstractDisposable
64
        implements FeatureReferenceSelection {
65

    
66
    protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultFeatureReferenceSelection.class);
67

    
68
    public static final String DYNCLASS_PERSISTENT_NAME = "DefaultFeatureReferenceSelection";
69

    
70
    private Boolean available = null;
71

    
72
    protected SelectionData selectionData = null;
73

    
74
    private FeatureStore featureStore;
75

    
76
    private FeatureSelectionHelper helper;
77

    
78
    private DelegateWeakReferencingObservable delegateObservable
79
            = new DelegateWeakReferencingObservable(this);
80

    
81
    /**
82
     * Creates a new Selection with the total size of Features from which the
83
     * selection will be performed.
84
     *
85
     * @param featureStore the FeatureStore of the selected FeatureReferences
86
     * @throws DataException if there is an error while getting the total number
87
     * of Features of the Store.
88
     */
89
    public DefaultFeatureReferenceSelection(DefaultFeatureStore featureStore)
90
            throws DataException {
91
        super();
92
        this.featureStore = featureStore;
93
        this.helper = new DefaultFeatureSelectionHelper(featureStore);
94
    }
95

    
96
    /**
97
     * Creates a new Selection with the total size of Features from which the
98
     * selection will be performed.
99
     *
100
     * @param featureStore the FeatureStore of the selected FeatureReferences
101
     * @param helper to get some information of the Store
102
     * @throws DataException if there is an error while getting the total number
103
     * of Features of the Store.
104
     */
105
    public DefaultFeatureReferenceSelection(FeatureStore featureStore,
106
            FeatureSelectionHelper helper)
107
            throws DataException {
108
        super();
109
        this.featureStore = featureStore;
110
        this.helper = helper;
111
    }
112

    
113
    /**
114
     * Constructor used by the persistence manager. Don't use directly. After to
115
     * invoke this method, the persistence manager calls the the method
116
     * {@link #loadFromState(PersistentState)} to set the values of the internal
117
     * attributes that this class needs to work.
118
     */
119
    public DefaultFeatureReferenceSelection() {
120
        super();
121
    }
122

    
123
    @Override
124
    public boolean select(FeatureReference reference) {
125
        return select(reference, true);
126
    }
127

    
128
    /**
129
     * @param reference
130
     * @param undoable if the action must be undoable
131
     * @return
132
     * @see #select(FeatureReference)
133
     */
134
    public boolean select(FeatureReference reference, boolean undoable) {
135
        if (reference == null) {
136
            throw new IllegalArgumentException("reference");
137
        }
138
        if (isSelected(reference)) {
139
            return false;
140
        }
141
        if (undoable && getFeatureStore().isEditing()) {
142
            getCommands().select(this, reference);
143
        }
144
        boolean change;
145
        if (this.getData().isReversed()) {
146
            change = this.getData().remove(reference);
147
        } else {
148
            change = this.getData().add(reference);
149
        }
150
        if (change) {
151
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
152
        }
153
        return change;
154
    }
155

    
156
    @Override
157
    public boolean deselect(FeatureReference reference) {
158
        return deselect(reference, true);
159
    }
160

    
161
    /**
162
     * @param reference
163
     * @param undoable if the action must be undoable
164
     * @return
165
     * @see #deselect(FeatureReference)
166
     */
167
    public boolean deselect(FeatureReference reference, boolean undoable) {
168
        if (!isSelected(reference)) {
169
            return false;
170
        }
171
        if (undoable && getFeatureStore().isEditing()) {
172
            getCommands().deselect(this, reference);
173
        }
174
        boolean change;
175
        if (this.getData().isReversed()) {
176
            change = this.getData().add(reference);
177
        } else {
178
            change = this.getData().remove(reference);
179
        }
180
        if (change) {
181
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
182
        }
183
        return change;
184
    }
185

    
186
    @Override
187
    public void selectAll() throws DataException {
188
        selectAll(true);
189
    }
190

    
191
    /**
192
     * @see #selectAll()
193
     * @param undoable if the action must be undoable
194
     * @throws org.gvsig.fmap.dal.exception.DataException
195
     */
196
    public void selectAll(boolean undoable) throws DataException {
197
        if (undoable && getFeatureStore().isEditing()) {
198
            getCommands().startComplex("_selectionSelectAll");
199
            getCommands().selectAll(this);
200
        }
201
        if (!this.getData().isReversed()) {
202
            this.getData().setReversed(true);
203
        }
204
        clearFeatureReferences();
205
        if (undoable && getFeatureStore().isEditing()) {
206
            getCommands().endComplex();
207
        }
208
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
209
    }
210

    
211
    @Override
212
    public void deselectAll() throws DataException {
213
        deselectAll(false);
214
    }
215

    
216
    /**
217
     * @param undoable if the action must be undoable
218
     * @throws org.gvsig.fmap.dal.exception.DataException
219
     * @see #deselectAll()
220
     */
221
    public void deselectAll(boolean undoable) throws DataException {
222
        if (this.selectionData == null) {
223
            return;
224
        }
225
        if (undoable && getFeatureStore().isEditing()) {
226
            getCommands().startComplex("_selectionDeselectAll");
227
            getCommands().deselectAll(this);
228
        }
229
        if (this.getData().isReversed()) {
230
            this.getData().setReversed(false);
231
        }
232
        clearFeatureReferences();
233
        if (undoable && getFeatureStore().isEditing()) {
234
            getCommands().endComplex();
235
        }
236
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
237
    }
238

    
239
    @Override
240
    public boolean isSelected(FeatureReference reference) {
241
        if (this.selectionData == null) {
242
            return false;
243
        }
244
        if (this.getData().isReversed()) {
245
            return !this.getData().contains(reference);
246
        } else {
247
            return this.getData().contains(reference);
248
        }
249
    }
250

    
251
    @Override
252
    public void reverse() {
253
        reverse(true);
254
    }
255

    
256
    /**
257
     * @see #reverse()
258
     * @param undoable if the action must be undoable
259
     */
260
    public void reverse(boolean undoable) {
261
        if (undoable && getFeatureStore().isEditing()) {
262
            getCommands().selectionReverse(this);
263
        }
264
        this.getData().setReversed(!this.getData().isReversed());
265
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
266
    }
267

    
268
    public boolean isEmpty() {
269
        if (this.selectionData == null) {
270
            return true;
271
        }
272
        return this.getSelectedCount() == 0;
273
    }
274

    
275
    @Override
276
    public long getSelectedCount() {
277
        if (this.selectionData == null) {
278
            return 0;
279
        }
280
        if (this.getData().isReversed()) {
281
            return this.getData().getTotalSize() - this.getData().getSize()
282
                    + helper.getFeatureStoreDeltaSize();
283
        } else {
284
            return this.getData().getSize();
285
        }
286
    }
287
    
288
    @Override
289
    public FeatureReferenceIterator referenceIterator() {
290
        Iterator<FeatureReference> it = this.getData().getSelected().iterator();
291
        return new FeatureReferenceIterator() {
292
            @Override
293
            public boolean hasNext() {
294
                return it.hasNext();
295
            }
296

    
297
            @Override
298
            public FeatureReference next() {
299
                return it.next();
300
            }
301
        };
302
    }
303

    
304
    @Override
305
    public Iterable<FeatureReference> referenceIterable() {
306
        return new Iterable<FeatureReference>() {
307
            @Override
308
            public Iterator<FeatureReference> iterator() {
309
                return referenceIterator();
310
            }
311
        };
312
//        Set<FeatureReference> s = Collections.unmodifiableSet(this.getData().getSelected());        
313
//        return s;
314
    }
315

    
316
    @Override
317
    protected void doDispose() throws BaseException {
318
        deselectAll(false);
319
        delegateObservable.deleteObservers();
320
    }
321

    
322
    @Override
323
    public boolean isFromStore(DataStore store) {
324
        return featureStore.equals(store);
325
    }
326

    
327
    @Override
328
    public void accept(Visitor visitor) throws BaseException {
329
        if (this.selectionData == null) {
330
            return;
331
        }
332
        for (Iterator iter = this.getData().getSelected().iterator(); iter.hasNext();) {
333
            visitor.visit(iter.next());
334
        }
335
    }
336

    
337
    @Override
338
    public void update(Observable observable, Object notification) {
339
        // If a Feature is deleted, remove it from the selection Set.
340
        if (notification instanceof FeatureStoreNotification) {
341
            FeatureStoreNotification storeNotif = (FeatureStoreNotification) notification;
342
            if (FeatureStoreNotification.AFTER_DELETE
343
                    .equalsIgnoreCase(storeNotif.getType())) {
344
                this.getData().remove(storeNotif.getFeature().getReference());
345
            }
346
        }
347
    }
348

    
349
    public SelectionData getData() {
350
        if (selectionData == null) {
351
            selectionData = new SelectionData();
352
            try {
353
                selectionData.setTotalSize(featureStore.getFeatureCount());
354
            } catch (DataException ex) {
355
                throw new RuntimeException("Can't initialize SelectionData, don't get the feature count.", ex);
356
            }
357
        }
358
        return selectionData;
359
    }
360

    
361
    public void setData(SelectionData selectionData) {
362
        this.selectionData = selectionData;
363
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
364
    }
365

    
366
    @Override
367
    public String toString() {
368
        try {
369
            ToStringBuilder builder = new ToStringBuilder(this);
370
            builder.append("featureStore", this.featureStore, true);
371
            builder.append("SelectedCount", this.getSelectedCount());
372
            builder.append("isReversed", this.isReversed());
373
            builder.append("featureIdsContained", selectionData==null?0:this.selectionData.getSelected());
374
            return builder.toString();
375
        } catch (Exception e) {
376
            return super.toString();
377
        }
378
    }
379
    
380
    protected boolean isReversed() {
381
        if (this.selectionData == null) {
382
            return false;
383
        }
384
        return this.getData().isReversed();
385
    }
386

    
387
    /**
388
     * Removes all the stored FeatureRefence objects.
389
     */
390
    protected void clearFeatureReferences() {
391
        if (this.selectionData == null) {
392
            return;
393
        }
394
        this.getData().clear();
395
    }
396

    
397
    /**
398
     * Returns the FeatureStore of the selected FeatureReferences.
399
     *
400
     * @return the featureStore
401
     */
402
    public FeatureStore getFeatureStore() {
403
        return featureStore;
404
    }
405

    
406
    /**
407
     * Returns the reference to the commands record.
408
     *
409
     * @return the reference to the commands record
410
     */
411
    protected FeatureCommandsStack getCommands() {
412
        return helper.getFeatureStoreCommandsStack();
413
    }
414

    
415
    public static class SelectionData implements Cloneable {
416

    
417
        private Set<FeatureReference> selected = new HashSet();
418

    
419
        /**
420
         * Sets how the Set of selected values has to be dealt.
421
         * <p>
422
         * If selected is FALSE, then values into the Set are the selected ones,
423
         * anything else is not selected.
424
         * </p>
425
         * <p>
426
         * If selected is TRUE, then values into the Set are values not
427
         * selected, anything else is selected.
428
         * </p>
429
         */
430
        private boolean reversed = false;
431

    
432
        private long totalSize;
433

    
434
        /**
435
         * @return the selected
436
         */
437
        public Set<FeatureReference> getSelected() {
438
            return selected;
439
        }
440

    
441
        /**
442
         * @param selected the selected to set
443
         */
444
        public void setSelected(Set<FeatureReference> selected) {
445
            this.selected = selected;
446
        }
447

    
448
        /**
449
         * @return the reversed
450
         */
451
        public boolean isReversed() {
452
            return reversed;
453
        }
454

    
455
        /**
456
         * @param reversed the reversed to set
457
         */
458
        public void setReversed(boolean reversed) {
459
            this.reversed = reversed;
460
        }
461

    
462
        /**
463
         * @return the totalSize
464
         */
465
        public long getTotalSize() {
466
            return totalSize;
467
        }
468

    
469
        /**
470
         * @param totalSize the totalSize to set
471
         */
472
        public void setTotalSize(long totalSize) {
473
            this.totalSize = totalSize;
474
        }
475

    
476
        public boolean add(FeatureReference reference) {
477
            return selected.add(reference);
478
        }
479

    
480
        public boolean remove(FeatureReference reference) {
481
            return selected.remove(reference);
482
        }
483

    
484
        public void clear() {
485
            selected.clear();
486
        }
487

    
488
        public boolean contains(FeatureReference reference) {
489
            return selected.contains(reference);
490
        }
491

    
492
        public int getSize() {
493
            return selected.size();
494
        }
495

    
496
        @Override
497
        public Object clone() throws CloneNotSupportedException {
498
            SelectionData clone = (SelectionData) super.clone();
499
            // reversed and totalSize already cloned by parent.
500
            // clone the selected Set
501
            clone.selected = new HashSet(selected);
502
            return clone;
503
        }
504
    }
505

    
506
    @Override
507
    public void saveToState(PersistentState state) throws PersistenceException {
508
        state.set("store", featureStore);
509
        state.set("reversed", this.getData().isReversed());
510
        state.set("totalSize", this.getData().getTotalSize());
511
        state.set("selected", this.getData().getSelected().iterator());
512
    }
513

    
514
    @Override
515
    public void loadFromState(PersistentState state)
516
            throws PersistenceException {
517
        SelectionData data = new SelectionData(); // Do not use this.getData()
518
        featureStore = (FeatureStore) state.get("store");
519
        helper = new DefaultFeatureSelectionHelper((DefaultFeatureStore) featureStore);
520
        data.setReversed(state.getBoolean("reversed"));
521
        data.setTotalSize(state.getLong("totalSize"));
522
        Iterator it = state.getIterator("selected");
523
        while (it.hasNext()) {
524
            FeatureReference ref = (FeatureReference) it.next();
525
            data.add(ref);
526
        }
527

    
528
        /*
529
             * If we do not do this, feature store will not listen
530
             * to changes in selection after instantiating a
531
             * persisted selection. For non-persisted instances,
532
             * this line corresponds to the line found in method:
533
             * getFeatureSelection() in DefaultFeatureStore.
534
             * This is not dangerous because "addObserver" only adds
535
             * if they were not already added, so future invocations
536
             * with same instances will have no effect.
537
         */
538
        this.addObserver((DefaultFeatureStore) featureStore);
539
    }
540

    
541
    public static void registerPersistent() {
542
        DynStruct definition = ToolsLocator.getPersistenceManager().addDefinition(
543
                DefaultFeatureReferenceSelection.class,
544
                DYNCLASS_PERSISTENT_NAME,
545
                "DefaultFeatureReferenceSelection Persistent definition",
546
                null,
547
                null
548
        );
549

    
550
        definition.addDynFieldObject("store").setClassOfValue(FeatureStore.class).setMandatory(true);
551
        definition.addDynFieldBoolean("reversed").setMandatory(true);
552
        definition.addDynFieldLong("totalSize").setMandatory(true);
553
        definition.addDynFieldList("selected").setClassOfItems(FeatureReference.class).setMandatory(true);
554

    
555
    }
556

    
557
    @Override
558
    public void addObserver(Observer observer) {
559
        delegateObservable.addObserver(observer);
560
    }
561

    
562
    @Override
563
    public void beginComplexNotification() {
564
        delegateObservable.beginComplexNotification();
565
    }
566

    
567
    @Override
568
    public void deleteObserver(Observer observer) {
569
        delegateObservable.deleteObserver(observer);
570
    }
571

    
572
    @Override
573
    public void deleteObservers() {
574
        delegateObservable.deleteObservers();
575
    }
576

    
577
    @Override
578
    public void disableNotifications() {
579
        delegateObservable.disableNotifications();
580
    }
581

    
582
    @Override
583
    public void enableNotifications() {
584
        delegateObservable.enableNotifications();
585
    }
586

    
587
    @Override
588
    public void endComplexNotification() {
589
        // We don't want to notify many times in a complex notification
590
        // scenario, so ignore notifications if in complex.
591
        // Only one notification will be sent when the complex notification
592
        // ends.
593
        delegateObservable.notifyObservers(DataStoreNotification.SELECTION_CHANGE);
594
        delegateObservable.endComplexNotification();
595
    }
596

    
597
    public boolean inComplex() {
598
        return delegateObservable.inComplex();
599
    }
600

    
601
    public boolean isEnabledNotifications() {
602
        return delegateObservable.isEnabledNotifications();
603
    }
604

    
605
    public void notifyObservers() {
606
        // We don't want to notify many times in a complex notification
607
        // scenario, so ignore notifications if in complex.
608
        // Only one notification will be sent when the complex notification
609
        // ends.
610
        if (!delegateObservable.inComplex()) {
611
            delegateObservable.notifyObservers();
612
        }
613
    }
614

    
615
    public void notifyObservers(Object arg) {
616
        if (!delegateObservable.inComplex()) {
617
            delegateObservable.notifyObservers(arg);
618
        }
619
    }
620

    
621
    @Override
622
    public Object clone() throws CloneNotSupportedException {
623
        DefaultFeatureReferenceSelection clone = (DefaultFeatureReferenceSelection) super.clone();
624
        // Original observers aren't cloned
625
        clone.delegateObservable = new DelegateWeakReferencingObservable(clone);
626
        // Clone internal data
627
        clone.selectionData = (SelectionData) this.getData().clone();
628
        // featureStore and helper are already swallow cloned by our parent
629
        return clone;
630
    }
631

    
632
    @Override
633
    public boolean isAvailable() {
634
        if (this.available == null) {
635
            try {
636
                FeatureType type = this.featureStore.getDefaultFeatureType();
637
                this.available = type.supportReferences();
638
            } catch (DataException ex) {
639
                this.available = false;
640
            }
641
        }
642
        return this.available;
643
    }
644

    
645
}