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

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

    
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.Feature;
35
import org.gvsig.fmap.dal.feature.FeatureReference;
36
import org.gvsig.fmap.dal.feature.FeatureReferenceSelection;
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 Iterator referenceIterator() {
290
        return Collections.unmodifiableSet(this.getData().getSelected())
291
                .iterator();
292
    }
293

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

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

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

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

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

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

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

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

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

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

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

    
387
    public static class SelectionData implements Cloneable {
388

    
389
        private Set selected = new HashSet();
390

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

    
404
        private long totalSize;
405

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

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

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

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

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

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

    
448
        public boolean add(FeatureReference reference) {
449
            return selected.add(reference);
450
        }
451

    
452
        public boolean remove(FeatureReference reference) {
453
            return selected.remove(reference);
454
        }
455

    
456
        public void clear() {
457
            selected.clear();
458
        }
459

    
460
        public boolean contains(FeatureReference reference) {
461
            return selected.contains(reference);
462
        }
463

    
464
        public int getSize() {
465
            return selected.size();
466
        }
467

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

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

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

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

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

    
522
        definition.addDynFieldObject("store").setClassOfValue(FeatureStore.class).setMandatory(true);
523
        definition.addDynFieldBoolean("reversed").setMandatory(true);
524
        definition.addDynFieldLong("totalSize").setMandatory(true);
525
        definition.addDynFieldList("selected").setClassOfItems(DefaultFeatureReference.class).setMandatory(true);
526

    
527
    }
528

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

    
534
    @Override
535
    public void beginComplexNotification() {
536
        delegateObservable.beginComplexNotification();
537
    }
538

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

    
544
    @Override
545
    public void deleteObservers() {
546
        delegateObservable.deleteObservers();
547
    }
548

    
549
    @Override
550
    public void disableNotifications() {
551
        delegateObservable.disableNotifications();
552
    }
553

    
554
    @Override
555
    public void enableNotifications() {
556
        delegateObservable.enableNotifications();
557
    }
558

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

    
569
    public boolean inComplex() {
570
        return delegateObservable.inComplex();
571
    }
572

    
573
    public boolean isEnabledNotifications() {
574
        return delegateObservable.isEnabledNotifications();
575
    }
576

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

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

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

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

    
617
}