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

History | View | Annotate | Download (19.3 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.FeatureType;
42
import org.gvsig.fmap.dal.feature.impl.undo.FeatureCommandsStack;
43
import org.gvsig.tools.ToolsLocator;
44
import org.gvsig.tools.dispose.impl.AbstractDisposable;
45
import org.gvsig.tools.dynobject.DynStruct;
46
import org.gvsig.tools.exception.BaseException;
47
import org.gvsig.tools.lang.Cloneable;
48
import org.gvsig.tools.observer.Observable;
49
import org.gvsig.tools.observer.Observer;
50
import org.gvsig.tools.observer.impl.BaseWeakReferencingObservable;
51
import org.gvsig.tools.observer.impl.DelegateWeakReferencingObservable;
52
import org.gvsig.tools.persistence.PersistentState;
53
import org.gvsig.tools.persistence.exception.PersistenceException;
54
import org.gvsig.tools.visitor.Visitor;
55

    
56
/**
57
 * Default implementation of a FeatureReferenceSelection, based on the usage of
58
 * a java.util.Set to store individual selected or not selected
59
 * FeatureReferences, depending on the usage of the {@link #reverse()} method.
60
 *
61
 * @author <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
62
 */
63
public class DefaultFeatureReferenceSelection extends AbstractDisposable
64
                implements FeatureReferenceSelection {
65

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

    
69
    private Boolean available = null;
70
    
71
    protected SelectionData selectionData = null;
72

    
73
    private FeatureStore featureStore;
74

    
75
    private FeatureSelectionHelper helper;
76

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

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

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

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

    
127
    public boolean select(FeatureReference reference) {
128
        return select(reference, true);
129
    }
130

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

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

    
156
        if (change) {
157
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
158
        }
159

    
160
        return change;
161
    }
162

    
163
    public boolean deselect(FeatureReference reference) {
164
        return deselect(reference, true);
165
    }
166

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

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

    
187
        if (change) {
188
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
189
        }
190

    
191
        return change;
192
    }
193

    
194
    public void selectAll() throws DataException {
195
        selectAll(true);
196
    }
197

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

    
218
    public void deselectAll() throws DataException {
219
        deselectAll(false);
220
    }
221

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

    
243
        notifyObservers(DataStoreNotification.SELECTION_CHANGE);
244
    }
245

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

    
257
    public void reverse() {
258
        reverse(true);
259
    }
260

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

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

    
293
    public Iterator referenceIterator() {
294
        return Collections.unmodifiableSet(this.getData().getSelected())
295
                .iterator();
296
    }
297

    
298
        protected void doDispose() throws BaseException {
299
                delegateObservable.deleteObservers();
300
                deselectAll(false);
301
    }
302

    
303
    public boolean isFromStore(DataStore store) {
304
        return featureStore.equals(store);
305
    }
306

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

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

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

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

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

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

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

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

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

    
388
        public static class SelectionData implements Cloneable {
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
415
         *            the selected to set
416
         */
417
        public void setSelected(Set selected) {
418
            this.selected = selected;
419
        }
420

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

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

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

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

    
451
        public boolean add(FeatureReference reference) {
452
            return selected.add(reference);
453
        }
454

    
455
        public boolean remove(FeatureReference reference) {
456
            return selected.remove(reference);
457
        }
458

    
459
        public void clear() {
460
            selected.clear();
461
        }
462

    
463
        public boolean contains(FeatureReference reference) {
464
            return selected.contains(reference);
465
        }
466

    
467
        public int getSize() {
468
            return selected.size();
469
        }
470

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

    
480
    // *** Persistence ***
481

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

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

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

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

    
526
                definition.addDynFieldObject("store").setClassOfValue(FeatureStore.class).setMandatory(true);
527
                definition.addDynFieldBoolean("reversed").setMandatory(true);
528
                definition.addDynFieldLong("totalSize").setMandatory(true);
529
                definition.addDynFieldList("selected").setClassOfItems(DefaultFeatureReference.class).setMandatory(true);
530

    
531
        }
532

    
533
        public void addObserver(Observer observer) {
534
                delegateObservable.addObserver(observer);
535
        }
536

    
537
        public void addObserver(Reference ref) {
538
                delegateObservable.addObserver(ref);
539
        }
540

    
541
        public void addObservers(BaseWeakReferencingObservable observable) {
542
                delegateObservable.addObservers(observable);
543
        }
544

    
545
        public void beginComplexNotification() {
546
                delegateObservable.beginComplexNotification();
547
        }
548

    
549
        public int countObservers() {
550
                return delegateObservable.countObservers();
551
        }
552

    
553
        public void deleteObserver(Observer observer) {
554
                delegateObservable.deleteObserver(observer);
555
        }
556

    
557
        public void deleteObserver(Reference ref) {
558
                delegateObservable.deleteObserver(ref);
559
        }
560

    
561
        public void deleteObservers() {
562
                delegateObservable.deleteObservers();
563
        }
564

    
565
        public void disableNotifications() {
566
                delegateObservable.disableNotifications();
567
        }
568

    
569
        public void enableNotifications() {
570
                delegateObservable.enableNotifications();
571
        }
572

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

    
583
        public boolean inComplex() {
584
                return delegateObservable.inComplex();
585
        }
586

    
587
        public boolean isEnabledNotifications() {
588
                return delegateObservable.isEnabledNotifications();
589
        }
590

    
591
        public void notifyObservers() {
592
                // We don't want to notify many times in a complex notification
593
                // scenario, so ignore notifications if in complex.
594
                // Only one notification will be sent when the complex notification
595
                // ends.
596
                if (!delegateObservable.inComplex()) {
597
                        delegateObservable.notifyObservers();
598
                }
599
        }
600

    
601
        public void notifyObservers(Object arg) {
602
                if (!delegateObservable.inComplex()) {
603
                        delegateObservable.notifyObservers(arg);
604
                }
605
        }
606

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

    
619
    @Override
620
    public boolean isAvailable() {
621
        if( this.available==null ) {
622
            try {
623
                FeatureType type = this.featureStore.getDefaultFeatureType();
624
                this.available = type.supportReferences();
625
            } catch (DataException ex) {
626
                this.available = false;
627
            }
628
        }
629
        return this.available;
630
    }
631
        
632
        
633
}