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 @ 45647

History | View | Annotate | Download (19.5 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.gvsig.fmap.dal.DataStore;
31
import org.gvsig.fmap.dal.DataStoreNotification;
32
import org.gvsig.fmap.dal.exception.DataException;
33
import org.gvsig.fmap.dal.feature.FeatureReference;
34
import org.gvsig.fmap.dal.feature.FeatureReferenceSelection;
35
import org.gvsig.fmap.dal.feature.FeatureStore;
36
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
37
import org.gvsig.fmap.dal.feature.FeatureType;
38
import org.gvsig.fmap.dal.feature.impl.undo.FeatureCommandsStack;
39
import org.gvsig.tools.ToolsLocator;
40
import org.gvsig.tools.dispose.impl.AbstractDisposable;
41
import org.gvsig.tools.dynobject.DynStruct;
42
import org.gvsig.tools.exception.BaseException;
43
import org.gvsig.tools.lang.Cloneable;
44
import org.gvsig.tools.observer.Observable;
45
import org.gvsig.tools.observer.Observer;
46
import org.gvsig.tools.observer.impl.DelegateWeakReferencingObservable;
47
import org.gvsig.tools.persistence.PersistentState;
48
import org.gvsig.tools.persistence.exception.PersistenceException;
49
import org.gvsig.tools.visitor.Visitor;
50
import org.slf4j.Logger;
51
import org.slf4j.LoggerFactory;
52

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

    
64
    protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultFeatureReferenceSelection.class);
65

    
66
    public static final String DYNCLASS_PERSISTENT_NAME = "DefaultFeatureReferenceSelection";
67

    
68
    private Boolean available = null;
69

    
70
    protected SelectionData selectionData = null;
71

    
72
    private FeatureStore featureStore;
73

    
74
    private FeatureSelectionHelper helper;
75

    
76
    private DelegateWeakReferencingObservable delegateObservable
77
            = new DelegateWeakReferencingObservable(this);
78

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

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

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

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

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

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

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

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

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

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

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

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

    
249
    @Override
250
    public void reverse() {
251
        reverse(true);
252
    }
253

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

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

    
273
    @Override
274
    public long getSelectedCount() {
275
        if (this.selectionData == null) {
276
            return 0;
277
        }
278
        if (this.getData().isReversed()) {
279
            return this.getData().getTotalSize() - this.getData().getSize()
280
                    + helper.getFeatureStoreDeltaSize();
281
        } else {
282
            return this.getData().getSize();
283
        }
284
    }
285

    
286
    @Override
287
    public Iterator referenceIterator() {
288
        return Collections.unmodifiableSet(this.getData().getSelected())
289
                .iterator();
290
    }
291

    
292
    @Override
293
    protected void doDispose() throws BaseException {
294
        delegateObservable.deleteObservers();
295
        deselectAll(false);
296
    }
297

    
298
    @Override
299
    public boolean isFromStore(DataStore store) {
300
        return featureStore.equals(store);
301
    }
302

    
303
    @Override
304
    public void accept(Visitor visitor) throws BaseException {
305
        if (this.selectionData == null) {
306
            return;
307
        }
308
        for (Iterator iter = this.getData().getSelected().iterator(); iter.hasNext();) {
309
            visitor.visit(iter.next());
310
        }
311
    }
312

    
313
    @Override
314
    public void update(Observable observable, Object notification) {
315
        // If a Feature is deleted, remove it from the selection Set.
316
        if (notification instanceof FeatureStoreNotification) {
317
            FeatureStoreNotification storeNotif = (FeatureStoreNotification) notification;
318
            if (FeatureStoreNotification.AFTER_DELETE
319
                    .equalsIgnoreCase(storeNotif.getType())) {
320
                this.getData().remove(storeNotif.getFeature().getReference());
321
            }
322
        }
323
    }
324

    
325
    public SelectionData getData() {
326
        if (selectionData == null) {
327
            selectionData = new SelectionData();
328
            try {
329
                selectionData.setTotalSize(featureStore.getFeatureCount());
330
            } catch (DataException ex) {
331
                throw new RuntimeException("Can't initialize SelectionData, don't get the feature count.", ex);
332
            }
333
        }
334
        return selectionData;
335
    }
336

    
337
    public void setData(SelectionData selectionData) {
338
        this.selectionData = selectionData;
339
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
340
    }
341

    
342
    @Override
343
    public String toString() {
344
        return getClass().getName() + ": " + getSelectedCount()
345
                + " features selected, reversed = "
346
                + this.getData().isReversed() + ", featureIds contained: "
347
                + this.getData().getSelected();
348
    }
349

    
350
    protected boolean isReversed() {
351
        if (this.selectionData == null) {
352
            return false;
353
        }
354
        return this.getData().isReversed();
355
    }
356

    
357
    /**
358
     * Removes all the stored FeatureRefence objects.
359
     */
360
    protected void clearFeatureReferences() {
361
        if (this.selectionData == null) {
362
            return;
363
        }
364
        this.getData().clear();
365
    }
366

    
367
    /**
368
     * Returns the FeatureStore of the selected FeatureReferences.
369
     *
370
     * @return the featureStore
371
     */
372
    protected FeatureStore getFeatureStore() {
373
        return featureStore;
374
    }
375

    
376
    /**
377
     * Returns the reference to the commands record.
378
     *
379
     * @return the reference to the commands record
380
     */
381
    protected FeatureCommandsStack getCommands() {
382
        return helper.getFeatureStoreCommandsStack();
383
    }
384

    
385
    public static class SelectionData implements Cloneable {
386

    
387
        private Set selected = new HashSet();
388

    
389
        /**
390
         * Sets how the Set of selected values has to be dealt.
391
         * <p>
392
         * If selected is FALSE, then values into the Set are the selected ones,
393
         * anything else is not selected.
394
         * </p>
395
         * <p>
396
         * If selected is TRUE, then values into the Set are values not
397
         * selected, anything else is selected.
398
         * </p>
399
         */
400
        private boolean reversed = false;
401

    
402
        private long totalSize;
403

    
404
        /**
405
         * @return the selected
406
         */
407
        public Set getSelected() {
408
            return selected;
409
        }
410

    
411
        /**
412
         * @param selected the selected to set
413
         */
414
        public void setSelected(Set selected) {
415
            this.selected = selected;
416
        }
417

    
418
        /**
419
         * @return the reversed
420
         */
421
        public boolean isReversed() {
422
            return reversed;
423
        }
424

    
425
        /**
426
         * @param reversed the reversed to set
427
         */
428
        public void setReversed(boolean reversed) {
429
            this.reversed = reversed;
430
        }
431

    
432
        /**
433
         * @return the totalSize
434
         */
435
        public long getTotalSize() {
436
            return totalSize;
437
        }
438

    
439
        /**
440
         * @param totalSize the totalSize to set
441
         */
442
        public void setTotalSize(long totalSize) {
443
            this.totalSize = totalSize;
444
        }
445

    
446
        public boolean add(FeatureReference reference) {
447
            return selected.add(reference);
448
        }
449

    
450
        public boolean remove(FeatureReference reference) {
451
            return selected.remove(reference);
452
        }
453

    
454
        public void clear() {
455
            selected.clear();
456
        }
457

    
458
        public boolean contains(FeatureReference reference) {
459
            return selected.contains(reference);
460
        }
461

    
462
        public int getSize() {
463
            return selected.size();
464
        }
465

    
466
        @Override
467
        public Object clone() throws CloneNotSupportedException {
468
            SelectionData clone = (SelectionData) super.clone();
469
            // reversed and totalSize already cloned by parent.
470
            // clone the selected Set
471
            clone.selected = new HashSet(selected);
472
            return clone;
473
        }
474
    }
475

    
476
    @Override
477
    public void saveToState(PersistentState state) throws PersistenceException {
478
        state.set("store", featureStore);
479
        state.set("reversed", this.getData().isReversed());
480
        state.set("totalSize", this.getData().getTotalSize());
481
        state.set("selected", this.getData().getSelected().iterator());
482
    }
483

    
484
    @Override
485
    public void loadFromState(PersistentState state)
486
            throws PersistenceException {
487
        SelectionData data = new SelectionData(); // Do not use this.getData()
488
        featureStore = (FeatureStore) state.get("store");
489
        helper = new DefaultFeatureSelectionHelper((DefaultFeatureStore) featureStore);
490
        data.setReversed(state.getBoolean("reversed"));
491
        data.setTotalSize(state.getLong("totalSize"));
492
        Iterator it = state.getIterator("selected");
493
        while (it.hasNext()) {
494
            FeatureReference ref = (FeatureReference) it.next();
495
            data.add(ref);
496
        }
497

    
498
        /*
499
             * If we do not do this, feature store will not listen
500
             * to changes in selection after instantiating a
501
             * persisted selection. For non-persisted instances,
502
             * this line corresponds to the line found in method:
503
             * getFeatureSelection() in DefaultFeatureStore.
504
             * This is not dangerous because "addObserver" only adds
505
             * if they were not already added, so future invocations
506
             * with same instances will have no effect.
507
         */
508
        this.addObserver((DefaultFeatureStore) featureStore);
509
    }
510

    
511
    public static void registerPersistent() {
512
        DynStruct definition = ToolsLocator.getPersistenceManager().addDefinition(
513
                DefaultFeatureReferenceSelection.class,
514
                DYNCLASS_PERSISTENT_NAME,
515
                "DefaultFeatureReferenceSelection Persistent definition",
516
                null,
517
                null
518
        );
519

    
520
        definition.addDynFieldObject("store").setClassOfValue(FeatureStore.class).setMandatory(true);
521
        definition.addDynFieldBoolean("reversed").setMandatory(true);
522
        definition.addDynFieldLong("totalSize").setMandatory(true);
523
        definition.addDynFieldList("selected").setClassOfItems(FeatureReference.class).setMandatory(true);
524

    
525
    }
526

    
527
    @Override
528
    public void addObserver(Observer observer) {
529
        delegateObservable.addObserver(observer);
530
    }
531

    
532
    @Override
533
    public void beginComplexNotification() {
534
        delegateObservable.beginComplexNotification();
535
    }
536

    
537
    @Override
538
    public void deleteObserver(Observer observer) {
539
        delegateObservable.deleteObserver(observer);
540
    }
541

    
542
    @Override
543
    public void deleteObservers() {
544
        delegateObservable.deleteObservers();
545
    }
546

    
547
    @Override
548
    public void disableNotifications() {
549
        delegateObservable.disableNotifications();
550
    }
551

    
552
    @Override
553
    public void enableNotifications() {
554
        delegateObservable.enableNotifications();
555
    }
556

    
557
    @Override
558
    public void endComplexNotification() {
559
        // We don't want to notify many times in a complex notification
560
        // scenario, so ignore notifications if in complex.
561
        // Only one notification will be sent when the complex notification
562
        // ends.
563
        delegateObservable.notifyObservers(DataStoreNotification.SELECTION_CHANGE);
564
        delegateObservable.endComplexNotification();
565
    }
566

    
567
    public boolean inComplex() {
568
        return delegateObservable.inComplex();
569
    }
570

    
571
    public boolean isEnabledNotifications() {
572
        return delegateObservable.isEnabledNotifications();
573
    }
574

    
575
    public void notifyObservers() {
576
        // We don't want to notify many times in a complex notification
577
        // scenario, so ignore notifications if in complex.
578
        // Only one notification will be sent when the complex notification
579
        // ends.
580
        if (!delegateObservable.inComplex()) {
581
            delegateObservable.notifyObservers();
582
        }
583
    }
584

    
585
    public void notifyObservers(Object arg) {
586
        if (!delegateObservable.inComplex()) {
587
            delegateObservable.notifyObservers(arg);
588
        }
589
    }
590

    
591
    @Override
592
    public Object clone() throws CloneNotSupportedException {
593
        DefaultFeatureReferenceSelection clone = (DefaultFeatureReferenceSelection) super.clone();
594
        // Original observers aren't cloned
595
        clone.delegateObservable = new DelegateWeakReferencingObservable(clone);
596
        // Clone internal data
597
        clone.selectionData = (SelectionData) this.getData().clone();
598
        // featureStore and helper are already swallow cloned by our parent
599
        return clone;
600
    }
601

    
602
    @Override
603
    public boolean isAvailable() {
604
        if (this.available == null) {
605
            try {
606
                FeatureType type = this.featureStore.getDefaultFeatureType();
607
                this.available = type.supportReferences();
608
            } catch (DataException ex) {
609
                this.available = false;
610
            }
611
        }
612
        return this.available;
613
    }
614

    
615
}