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

History | View | Annotate | Download (18.8 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 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.lang.ref.Reference;
27
import java.util.Collections;
28
import java.util.HashSet;
29
import java.util.Iterator;
30
import java.util.Set;
31
import java.util.logging.Level;
32
import java.util.logging.Logger;
33

    
34
import org.gvsig.fmap.dal.DataStore;
35
import org.gvsig.fmap.dal.DataStoreNotification;
36
import org.gvsig.fmap.dal.exception.DataException;
37
import org.gvsig.fmap.dal.feature.FeatureReference;
38
import org.gvsig.fmap.dal.feature.FeatureReferenceSelection;
39
import org.gvsig.fmap.dal.feature.FeatureStore;
40
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
41
import org.gvsig.fmap.dal.feature.impl.undo.FeatureCommandsStack;
42
import org.gvsig.tools.ToolsLocator;
43
import org.gvsig.tools.dispose.impl.AbstractDisposable;
44
import org.gvsig.tools.dynobject.DynStruct;
45
import org.gvsig.tools.exception.BaseException;
46
import org.gvsig.tools.lang.Cloneable;
47
import org.gvsig.tools.observer.Observable;
48
import org.gvsig.tools.observer.Observer;
49
import org.gvsig.tools.observer.impl.BaseWeakReferencingObservable;
50
import org.gvsig.tools.observer.impl.DelegateWeakReferencingObservable;
51
import org.gvsig.tools.persistence.PersistentState;
52
import org.gvsig.tools.persistence.exception.PersistenceException;
53
import org.gvsig.tools.visitor.Visitor;
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 <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
61
 */
62
public class DefaultFeatureReferenceSelection extends AbstractDisposable
63
                implements FeatureReferenceSelection {
64

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

    
68
    protected SelectionData selectionData = null;
69

    
70
    private FeatureStore featureStore;
71

    
72
    private FeatureSelectionHelper helper;
73

    
74
        private DelegateWeakReferencingObservable delegateObservable =
75
                        new DelegateWeakReferencingObservable(this);
76

    
77
        /**
78
         * Creates a new Selection with the total size of Features from which the
79
         * selection will be performed.
80
         *
81
         * @param featureStore
82
         *            the FeatureStore of the selected FeatureReferences
83
         * @throws DataException
84
         *             if there is an error while getting the total number of
85
         *             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
99
     *            the FeatureStore of the selected FeatureReferences
100
     * @param helper
101
     *            to get some information of the Store
102
     * @throws DataException
103
     *             if there is an error while getting the total number of
104
     *             Features of the Store.
105
     */
106
    public DefaultFeatureReferenceSelection(FeatureStore featureStore,
107
            FeatureSelectionHelper helper)
108
            throws DataException {
109
        super();
110
        this.featureStore = featureStore;
111
        this.helper = helper;
112
    }
113

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

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

    
128
    /**
129
     * @see #select(FeatureReference)
130
     * @param undoable
131
     *            if the action must be undoable
132
     */
133
    public boolean select(FeatureReference reference, boolean undoable) {
134
        
135
        if (reference == null) {
136
            throw new IllegalArgumentException("reference");
137
        }
138
        
139
        if (isSelected(reference)) {
140
            return false;
141
        }
142

    
143
        if (undoable && getFeatureStore().isEditing()) {
144
            getCommands().select(this, reference);
145
        }
146
        boolean change = false;
147
        if ( this.getData().isReversed()) {
148
            change = this.getData().remove(reference);
149
        } else {
150
            change = this.getData().add(reference);
151
        }
152

    
153
        if (change) {
154
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
155
        }
156

    
157
        return change;
158
    }
159

    
160
    public boolean deselect(FeatureReference reference) {
161
        return deselect(reference, true);
162
    }
163

    
164
    /**
165
     * @see #deselect(FeatureReference)
166
     * @param undoable
167
     *            if the action must be undoable
168
     */
169
    public boolean deselect(FeatureReference reference, boolean undoable) {
170
        if (!isSelected(reference)) {
171
            return false;
172
        }
173

    
174
        if (undoable && getFeatureStore().isEditing()) {
175
            getCommands().deselect(this, reference);
176
        }
177
        boolean change = false;
178
        if (this.getData().isReversed()) {
179
            change = this.getData().add(reference);
180
        } else {
181
            change = this.getData().remove(reference);
182
        }
183

    
184
        if (change) {
185
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
186
        }
187

    
188
        return change;
189
    }
190

    
191
    public void selectAll() throws DataException {
192
        selectAll(true);
193
    }
194

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

    
215
    public void deselectAll() throws DataException {
216
        deselectAll(false);
217
    }
218

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

    
240
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
241
    }
242

    
243
    public boolean isSelected(FeatureReference reference) {
244
        if( this.selectionData==null ) {
245
            return false;
246
        }
247
        if (this.getData().isReversed()) {
248
            return !this.getData().contains(reference);
249
        } else {
250
            return this.getData().contains(reference);
251
        }
252
    }
253

    
254
    public void reverse() {
255
        reverse(true);
256
    }
257

    
258
    /**
259
     * @see #reverse()
260
     * @param undoable
261
     *            if the action must be undoable
262
     */
263
    public void reverse(boolean undoable) {
264
        if (undoable && getFeatureStore().isEditing()) {
265
            getCommands().selectionReverse(this);
266
        }
267
        this.getData().setReversed(!this.getData().isReversed());
268
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
269
    }
270
    
271
    public boolean isEmpty() {
272
        if( this.selectionData == null ) {
273
            return true;
274
        }
275
        return this.getSelectedCount()==0;
276
    }
277

    
278
    public long getSelectedCount() {
279
        if( this.selectionData == null ) {
280
            return 0;
281
        }
282
        if (this.getData().isReversed()) {
283
                return this.getData().getTotalSize() - this.getData().getSize()
284
                        + helper.getFeatureStoreDeltaSize();
285
        } else {
286
            return this.getData().getSize();
287
        }
288
    }
289

    
290
    public Iterator referenceIterator() {
291
        return Collections.unmodifiableSet(this.getData().getSelected())
292
                .iterator();
293
    }
294

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

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

    
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
309
                .hasNext();) {
310
            visitor.visit(iter.next());
311
        }
312
    }
313

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

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

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

    
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
        private Set selected = new HashSet();
387

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

    
401
        private long totalSize;
402

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

    
410
        /**
411
         * @param selected
412
         *            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
427
         *            the reversed to set
428
         */
429
        public void setReversed(boolean reversed) {
430
            this.reversed = reversed;
431
        }
432

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

    
440
        /**
441
         * @param totalSize
442
         *            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
        public Object clone() throws CloneNotSupportedException {
469
                        SelectionData clone = (SelectionData) super.clone();
470
                        // reversed and totalSize already cloned by parent.
471
                        // clone the selected Set
472
                        clone.selected = new HashSet(selected);
473
            return clone;
474
        }
475
    }
476

    
477
    // *** Persistence ***
478

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

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

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

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

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

    
528
        }
529

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

    
534
        public void addObserver(Reference ref) {
535
                delegateObservable.addObserver(ref);
536
        }
537

    
538
        public void addObservers(BaseWeakReferencingObservable observable) {
539
                delegateObservable.addObservers(observable);
540
        }
541

    
542
        public void beginComplexNotification() {
543
                delegateObservable.beginComplexNotification();
544
        }
545

    
546
        public int countObservers() {
547
                return delegateObservable.countObservers();
548
        }
549

    
550
        public void deleteObserver(Observer observer) {
551
                delegateObservable.deleteObserver(observer);
552
        }
553

    
554
        public void deleteObserver(Reference ref) {
555
                delegateObservable.deleteObserver(ref);
556
        }
557

    
558
        public void deleteObservers() {
559
                delegateObservable.deleteObservers();
560
        }
561

    
562
        public void disableNotifications() {
563
                delegateObservable.disableNotifications();
564
        }
565

    
566
        public void enableNotifications() {
567
                delegateObservable.enableNotifications();
568
        }
569

    
570
        public void endComplexNotification() {
571
                // We don't want to notify many times in a complex notification
572
                // scenario, so ignore notifications if in complex.
573
                // Only one notification will be sent when the complex notification
574
                // ends.
575
                delegateObservable
576
                                .notifyObservers(DataStoreNotification.SELECTION_CHANGE);
577
                delegateObservable.endComplexNotification();
578
        }
579

    
580
        public boolean inComplex() {
581
                return delegateObservable.inComplex();
582
        }
583

    
584
        public boolean isEnabledNotifications() {
585
                return delegateObservable.isEnabledNotifications();
586
        }
587

    
588
        public void notifyObservers() {
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
                if (!delegateObservable.inComplex()) {
594
                        delegateObservable.notifyObservers();
595
                }
596
        }
597

    
598
        public void notifyObservers(Object arg) {
599
                if (!delegateObservable.inComplex()) {
600
                        delegateObservable.notifyObservers(arg);
601
                }
602
        }
603

    
604
    @Override
605
        public Object clone() throws CloneNotSupportedException {
606
                DefaultFeatureReferenceSelection clone = (DefaultFeatureReferenceSelection) super
607
                                .clone();
608
                // Original observers aren't cloned
609
                clone.delegateObservable = new DelegateWeakReferencingObservable(clone);
610
                // Clone internal data
611
                clone.selectionData = (SelectionData) this.getData().clone();
612
                // featureStore and helper are already swallow cloned by our parent
613
                return clone;
614
        }
615
}