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 / DefaultFeatureSelection.java @ 43089

History | View | Annotate | Download (23.4 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 modify it under
7
 * the terms of the GNU General Public License as published by the Free Software
8
 * Foundation; either version 3 of the License, or (at your option) any later
9
 * version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 * details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with
17
 * this program; if not, write to the Free Software Foundation, Inc., 51
18
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us at info AT
21
 * gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.fmap.dal.feature.impl;
24

    
25
import java.util.ArrayList;
26
import java.util.HashMap;
27
import java.util.Iterator;
28
import java.util.List;
29
import java.util.Map;
30
import java.util.Map.Entry;
31

    
32
import org.gvsig.fmap.dal.DataStoreNotification;
33
import org.gvsig.fmap.dal.exception.DataException;
34
import org.gvsig.fmap.dal.exception.DataRuntimeException;
35
import org.gvsig.fmap.dal.feature.EditableFeature;
36
import org.gvsig.fmap.dal.feature.Feature;
37
import org.gvsig.fmap.dal.feature.FeatureReference;
38
import org.gvsig.fmap.dal.feature.FeatureSelection;
39
import org.gvsig.fmap.dal.feature.FeatureSet;
40
import org.gvsig.fmap.dal.feature.FeatureStore;
41
import org.gvsig.fmap.dal.feature.FeatureType;
42
import org.gvsig.fmap.dal.feature.exception.ReversedSelectionIteratorException;
43
import org.gvsig.fmap.dal.feature.impl.DefaultFeatureReferenceSelection.SelectionData;
44
import org.gvsig.fmap.dal.feature.impl.featureset.AbstractFeatureSet;
45
import org.gvsig.fmap.dal.feature.impl.undo.FeatureCommandsStack;
46
import org.gvsig.tools.ToolsLocator;
47
import org.gvsig.tools.dispose.DisposableIterator;
48
import org.gvsig.tools.dispose.DisposeUtils;
49
import org.gvsig.tools.dynobject.DynStruct;
50
import org.gvsig.tools.exception.BaseException;
51
import org.gvsig.tools.observer.Observable;
52
import org.gvsig.tools.observer.Observer;
53
import org.gvsig.tools.persistence.PersistenceManager;
54
import org.gvsig.tools.persistence.PersistentState;
55
import org.gvsig.tools.persistence.exception.PersistenceException;
56
import org.slf4j.Logger;
57
import org.slf4j.LoggerFactory;
58

    
59
/**
60
 * Default implementation of the FeatureSelection interface. Internally, only
61
 * FeatureReference values are stored.
62
 *
63
 * This implementation performs better if used with the selection related
64
 * methods: select, deselect and isSelected ones.
65
 *
66
 * @author <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
67
 */
68
public class DefaultFeatureSelection extends AbstractFeatureSet
69
        implements FeatureSelection {
70

    
71
    private static final Logger LOG = LoggerFactory
72
            .getLogger(DefaultFeatureSelection.class);
73

    
74
    public class RemoveFromFeatureSelectionException extends DataRuntimeException {
75

    
76
        /**
77
         *
78
         */
79
        private static final long serialVersionUID = 2636692469445838928L;
80
        private final static String MESSAGE_FORMAT = "Can't remove feature from reversed selection.";
81
        private final static String MESSAGE_KEY = "_RemoveFromFeatureSelectionException";
82

    
83
        public RemoveFromFeatureSelectionException(Throwable cause) {
84
            super(MESSAGE_FORMAT, cause, MESSAGE_KEY, serialVersionUID);
85
            //setValue("store", store);
86
        }
87
    }
88

    
89
    /**
90
     * Facade over a Iterator of FeatureReferences, to return Features instead.
91
     *
92
     * @author <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
93
     */
94
    private class FeatureIteratorFacade implements DisposableIterator {
95

    
96
        private final Logger LOGGER = LoggerFactory
97
                .getLogger(FeatureIteratorFacade.class);
98

    
99
        private java.util.Iterator refIterator;
100

    
101
        private FeatureStore featureStore;
102
        private Feature currentFeature = null;
103

    
104
        public FeatureIteratorFacade(java.util.Iterator iter,
105
                FeatureStore featureStore) {
106
            this.refIterator = iter;
107
            this.featureStore = featureStore;
108
        }
109

    
110
        @Override
111
        public boolean hasNext() {
112
            return refIterator.hasNext();
113
        }
114

    
115
        @Override
116
        public Object next() {
117
            FeatureReference ref = nextFeatureReference();
118
            try {
119
                currentFeature = featureStore.getFeatureByReference(ref);
120
                return currentFeature;
121
            } catch (DataException ex) {
122
                LOGGER.error(
123
                        "Error loading the Feature with FeatureReference: "
124
                        + ref, ex);
125
                return null;
126
            }
127
        }
128

    
129
        /**
130
         * Returns the next FeatureReference.
131
         *
132
         * @return the next FeatureReference
133
         */
134
        public FeatureReference nextFeatureReference() {
135
            return (FeatureReference) refIterator.next();
136
        }
137

    
138
        @Override
139
        public void remove() {
140
            try {
141
                featureStore.delete(currentFeature);
142
                refIterator.remove();
143
            } catch (DataException e) {
144
                throw new RemoveFromFeatureSelectionException(e);
145
            }
146
        }
147

    
148
        public class RemoveFromFeatureSelectionException extends DataRuntimeException {
149

    
150
            /**
151
             *
152
             */
153
            private static final long serialVersionUID = 2636692469445838928L;
154
            private final static String MESSAGE_FORMAT = "Can't remove feature from selection.";
155
            private final static String MESSAGE_KEY = "_RemoveFromFeatureSelectionException";
156

    
157
            public RemoveFromFeatureSelectionException(Throwable cause) {
158
                super(MESSAGE_FORMAT, cause, MESSAGE_KEY, serialVersionUID);
159
                //setValue("store", store);
160
            }
161
        }
162

    
163
        @Override
164
        public void dispose() {
165
            if (refIterator instanceof DisposableIterator) {
166
                ((DisposableIterator) refIterator).dispose();
167
            }
168
            refIterator = null;
169
            featureStore = null;
170
        }
171
    }
172

    
173
    /**
174
     * Facade over a Iterator of FeatureReferences, to return Features instead,
175
     * when the Selection is reversed
176
     *
177
     * @author <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
178
     */
179
    private class ReversedFeatureIteratorFacade implements DisposableIterator {
180

    
181
        private SelectionData selectionData;
182

    
183
        private DisposableIterator iterator;
184

    
185
        private Feature nextFeature = null;
186
        private Feature currentFeature = null;
187

    
188
        private FeatureSet featureSet;
189

    
190
        public ReversedFeatureIteratorFacade(SelectionData selectionData,
191
                FeatureStore featureStore, boolean fastIterator) {
192
            this.selectionData = selectionData;
193

    
194
            // Load a Set with all the store features
195
            try {
196
                featureSet = featureStore.getFeatureSet();
197
                //if (fastIterator) {
198
                iterator = featureSet.fastIterator();
199
//                                } else {
200
//                                        iterator = featureSet.iterator();
201
//                                }
202
            } catch (DataException ex) {
203
                throw new ReversedSelectionIteratorException(ex);
204
            }
205

    
206
            // Filter the features not selected and position in the next
207
            // selected feature
208
            positionInNextElement();
209
        }
210

    
211
        @Override
212
        public boolean hasNext() {
213
            return nextFeature != null;
214
        }
215

    
216
        @Override
217
        public Object next() {
218
            featureIterators.remove(currentFeature);
219
            currentFeature = nextFeature.getCopy();
220
            featureIterators.put(currentFeature, this);
221
            positionInNextElement();
222
            return currentFeature;
223
        }
224

    
225
        @Override
226
        public void remove() {
227
            try {
228
                featureSet.delete(currentFeature);
229
            } catch (DataException e) {
230
                throw new RemoveFromFeatureSelectionException(e);
231

    
232
            }
233
        }
234

    
235
        private void positionInNextElement() {
236
            nextFeature = null;
237
            while (iterator.hasNext()) {
238
                nextFeature = (Feature) iterator.next();
239
                if (selectionData.contains(nextFeature.getReference())) {
240
                    nextFeature = null;
241
                } else {
242
                    break;
243
                }
244
            }
245
        }
246

    
247
        @Override
248
        public void dispose() {
249
            this.featureSet.dispose();
250
            this.iterator.dispose();
251
            this.selectionData = null;
252
            this.nextFeature = null;
253
        }
254
    }
255

    
256
    private Map featureTypeCounts = new HashMap(1);
257
    private final Map<Feature, Iterator> featureIterators = new HashMap<>();
258
    private final DefaultFeatureReferenceSelection featureReferenceSelection;
259

    
260
    /**
261
     * Creates a DefaultFeatureSelection, with a FeatureStore.
262
     *
263
     * @param featureStore the FeatureStore to load Features from
264
     * @throws DataException if there is an error while getting the total number
265
     * of Features of the Store.
266
     * @see AbstractSetBasedDataSelection#DefaultSelection(int)
267
     */
268
    public DefaultFeatureSelection(DefaultFeatureStore featureStore)
269
            throws DataException {
270
        this.featureReferenceSelection = new DefaultFeatureReferenceSelection(featureStore);
271
    }
272

    
273
    /**
274
     * Creates a new Selection with the total size of Features from which the
275
     * selection will be performed.
276
     *
277
     * @param featureStore the FeatureStore of the selected FeatureReferences
278
     * @param helper to get some information of the Store
279
     * @throws DataException if there is an error while getting the total number
280
     * of Features of the Store.
281
     */
282
    public DefaultFeatureSelection(FeatureStore featureStore,
283
            FeatureSelectionHelper helper) throws DataException {
284
        this.featureReferenceSelection = new DefaultFeatureReferenceSelection(featureStore, helper);
285
    }
286

    
287
    /**
288
     * Constructor used by the persistence manager. Don't use directly. After to
289
     * invoke this method, the persistence manager calls the the method
290
     * {@link #loadFromState(PersistentState)} to set the values of the internal
291
     * attributes that this class needs to work.
292
     */
293
    public DefaultFeatureSelection() {
294
        this.featureReferenceSelection = new DefaultFeatureReferenceSelection();
295
    }
296

    
297
    @Override
298
    public FeatureStore getFeatureStore() {
299
        return this.featureReferenceSelection.getFeatureStore();
300
    }
301

    
302
    private void notifyObservers(String notificationType) {
303
        this.featureReferenceSelection.notifyObservers(notificationType);
304
    }
305

    
306
    public FeatureCommandsStack getCommands() {
307
        return this.featureReferenceSelection.getCommands();
308
    }
309

    
310
    @Override
311
    public void enableNotifications() {
312
        this.featureReferenceSelection.enableNotifications();
313
    }
314

    
315
    @Override
316
    public void disableNotifications() {
317
        this.featureReferenceSelection.disableNotifications();
318
    }
319

    
320
    public boolean isReversed() {
321
        return this.featureReferenceSelection.isReversed();
322
    }
323

    
324
    @Override
325
    public long getSelectedCount() {
326
        return this.featureReferenceSelection.getSelectedCount();
327
    }
328

    
329
    public DefaultFeatureReferenceSelection.SelectionData getData() {
330
        return this.featureReferenceSelection.getData();
331
    }
332

    
333
    @Override
334
    public boolean select(FeatureReference reference) {
335
        return this.featureReferenceSelection.select(reference);
336
    }
337

    
338
    public boolean select(FeatureReference reference, boolean undoable) {
339
        return this.featureReferenceSelection.select(reference, undoable);
340
    }
341

    
342
    @Override
343
    public boolean deselect(FeatureReference reference) {
344
        return this.featureReferenceSelection.deselect(reference);
345
    }
346

    
347
    public boolean deselect(FeatureReference reference, boolean undoable) {
348
        return this.featureReferenceSelection.deselect(reference, undoable);
349
    }
350

    
351
    @Override
352
    public Iterator referenceIterator() {
353
        return this.featureReferenceSelection.referenceIterator();
354
    }
355

    
356
    @Override
357
    public void selectAll() throws DataException {
358
        this.featureReferenceSelection.selectAll();
359
    }
360

    
361
    @Override
362
    public void deselectAll() throws DataException {
363
        this.featureReferenceSelection.deselectAll();
364
    }
365
    
366
    public void deselectAll(boolean undoable) throws DataException {
367
        this.featureReferenceSelection.deselectAll(undoable);
368
    }
369
    
370
    @Override
371
    public boolean isSelected(FeatureReference reference) {
372
        return this.featureReferenceSelection.isSelected(reference);
373
    }
374

    
375
    @Override
376
    public void reverse() {
377
        this.featureReferenceSelection.reverse();
378
    }
379

    
380
    @Override
381
    public void dispose() {
382
        this.featureReferenceSelection.dispose();
383
    }
384

    
385
    @Override
386
    public void update(Observable o, Object o1) {
387
        this.featureReferenceSelection.update(o, o1);
388
    }
389

    
390
    @Override
391
    public void addObserver(Observer obsrvr) {
392
        this.featureReferenceSelection.addObserver(obsrvr);
393
    }
394

    
395
    @Override
396
    public void deleteObserver(Observer obsrvr) {
397
        this.featureReferenceSelection.deleteObserver(obsrvr);
398
    }
399

    
400
    @Override
401
    public void deleteObservers() {
402
        this.featureReferenceSelection.deleteObservers();
403
    }
404

    
405
    @Override
406
    public void beginComplexNotification() {
407
        this.featureReferenceSelection.beginComplexNotification();
408
    }
409

    
410
    @Override
411
    public void endComplexNotification() {
412
        this.featureReferenceSelection.endComplexNotification();
413
    }
414

    
415
    @Override
416
    public void saveToState(PersistentState ps) throws PersistenceException {
417
        this.featureReferenceSelection.saveToState(ps);
418
    }
419

    
420
    @Override
421
    public boolean select(Feature feature) {
422
        return select(feature, true);
423
    }
424

    
425
    /**
426
     * @param feature
427
     * @return
428
     * @see #select(Feature)
429
     * @param undoable if the action must be undoable
430
     */
431
    public boolean select(Feature feature, boolean undoable) {
432
        // TODO: should we check if the feature is from the same FeatureStore??
433
        if (feature == null) {
434
            return false;
435
        }
436

    
437
        // LOGGER.debug("Selected feature: {}", feature);
438
        if (isReversed()) {
439
            removeFeatureTypeCount(feature.getType());
440
        } else {
441
            addFeatureTypeCount(feature.getType());
442
        }
443
        return select(feature.getReference(), undoable);
444
    }
445

    
446
    @Override
447
    public boolean select(FeatureSet features) throws DataException {
448
        return select(features, true);
449
    }
450

    
451
    /**
452
     * @param features
453
     * @return
454
     * @throws org.gvsig.fmap.dal.exception.DataException
455
     * @see #select(FeatureSet)
456
     * @param undoable if the action must be undoable
457
     */
458
    public boolean select(FeatureSet features, boolean undoable)
459
            throws DataException {
460
        boolean change = false;
461
        boolean inComplex = false;
462
        if (undoable && this.featureReferenceSelection.getFeatureStore().isEditing()
463
                && !this.featureReferenceSelection.getCommands().inComplex()) {
464

    
465
            this.featureReferenceSelection.getCommands().startComplex("_selectionSelectFeatureSet");
466
            inComplex = this.featureReferenceSelection.getCommands().inComplex();
467
        }
468

    
469
        disableNotifications();
470
        DisposableIterator iter = null;
471
        try {
472
            for (iter = features.fastIterator(); iter.hasNext();) {
473
                change |= select((Feature) iter.next(), undoable);
474
            }
475
        } finally {
476
            DisposeUtils.disposeQuietly(iter);
477
        }
478
        enableNotifications();
479
        if (undoable && getFeatureStore().isEditing() && inComplex) {
480
            getCommands().endComplex();
481
        }
482
        if (change) {
483
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
484
        }
485
        return change;
486
    }
487

    
488
    @Override
489
    public boolean deselect(Feature feature) {
490
        return deselect(feature, true);
491
    }
492

    
493
    /**
494
     * @param feature
495
     * @return
496
     * @see #deselect(Feature)
497
     * @param undoable if the action must be undoable
498
     */
499
    public boolean deselect(Feature feature, boolean undoable) {
500
        if (feature == null) {
501
            return false;
502
        }
503

    
504
        LOG.debug("Deselected feature: {}", feature);
505

    
506
        if (isReversed()) {
507
            addFeatureTypeCount(feature.getType());
508
        } else {
509
            removeFeatureTypeCount(feature.getType());
510
        }
511
        return deselect(feature.getReference(), undoable);
512
    }
513

    
514
    @Override
515
    public boolean deselect(FeatureSet features) throws DataException {
516
        return deselect(features, true);
517
    }
518

    
519
    /**
520
     * @param features
521
     * @return
522
     * @throws org.gvsig.fmap.dal.exception.DataException
523
     * @see #deselect(FeatureSet)
524
     * @param undoable if the action must be undoable
525
     */
526
    public boolean deselect(FeatureSet features, boolean undoable)
527
            throws DataException {
528
        boolean change = false;
529
        if (undoable && getFeatureStore().isEditing()) {
530
            getCommands().startComplex("_selectionDeselectFeatureSet");
531
        }
532
        disableNotifications();
533
        DisposableIterator iter = null;
534
        try {
535
            for (iter = features.fastIterator(); iter.hasNext();) {
536
                change |= deselect((Feature) iter.next(), undoable);
537
            }
538
        } finally {
539
            DisposeUtils.disposeQuietly(iter);
540
        }
541
        enableNotifications();
542
        if (undoable && getFeatureStore().isEditing()) {
543
            getCommands().endComplex();
544
        }
545
        if (change) {
546
            notifyObservers(DataStoreNotification.SELECTION_CHANGE);
547
        }
548
        return change;
549
    }
550

    
551
    @Override
552
    public boolean isSelected(Feature feature) {
553
        if (feature == null) {
554
            return false;
555
        }
556

    
557
        // Use the selection data size as a small optimization for the most
558
        // common case, when nothing is selected and every feature is checked
559
        // while drawing or painting the table document.
560
        if (getData().isReversed()) {
561
            return getData().getSize() == 0
562
                    || !getData().contains(feature.getReference());
563
        } else {
564
            return getData().getSize() > 0
565
                    && getData().contains(feature.getReference());
566
        }
567
    }
568

    
569
    @Override
570
    public FeatureType getDefaultFeatureType() {
571
        try {
572
            return getFeatureStore().getDefaultFeatureType();
573
        } catch (DataException ex) {
574
            LOG.error("Error getting the default feature type "
575
                    + "of the FeatureStore: " + getFeatureStore(), ex);
576
        }
577
        return null;
578
    }
579

    
580
    @Override
581
    public List getFeatureTypes() {
582
        // Go through the map of FeatureTypes, and return only the ones that
583
        // have at least a Feature.
584
        List types = new ArrayList();
585
        for (java.util.Iterator iterator = featureTypeCounts.entrySet()
586
                .iterator(); iterator.hasNext();) {
587
            Map.Entry entry = (Entry) iterator.next();
588
            FeatureType type = (FeatureType) entry.getKey();
589
            Long count = (Long) entry.getValue();
590

    
591
            if (count > 0) {
592
                types.add(type);
593
            }
594
        }
595

    
596
        return types;
597
    }
598

    
599
    @Override
600
    public long getSize() throws DataException {
601
        return getSelectedCount();
602
    }
603

    
604
    /**
605
     * Returns the list of selected values, or the deselected ones if the
606
     * selection has been reversed.
607
     *
608
     * WARN: not very good performance implementation.
609
     */
610
    @Override
611
    public DisposableIterator iterator(long index) {
612
        return iterator(index, false);
613
    }
614

    
615
    /**
616
     * Returns the list of selected values, or the deselected ones if the
617
     * selection has been reversed.
618
     *
619
     * WARN: not really a fast implementation.
620
     */
621
    @Override
622
    public DisposableIterator fastIterator(long index) {
623
        return iterator(index, true);
624
    }
625

    
626
    protected void clearFeatureReferences() {
627
        this.featureReferenceSelection.clearFeatureReferences();
628
        featureTypeCounts.clear();
629
    }
630

    
631
    /**
632
     * Creates an iterator for the Selection.
633
     */
634
    private DisposableIterator iterator(long index, boolean fastIterator) {
635
        if (isReversed()) {
636
            DisposableIterator iter = new ReversedFeatureIteratorFacade(
637
                    getData(), getFeatureStore(), fastIterator);
638
            for (long l = 0; l < index && iter.hasNext(); l++) {
639
                iter.next();
640
            }
641
            return iter;
642

    
643
        } else {
644
            // TODO: maybe we could add a new referenceIterator(int index)
645
            // method that could be implemented in a more performant way
646

    
647
            java.util.Iterator iter = getData().getSelected().iterator();
648
            for (long l = 0; l < index && iter.hasNext(); l++) {
649
                iter.next();
650
            }
651
            return new FeatureIteratorFacade(iter, getFeatureStore());
652
        }
653
    }
654

    
655
    private Long removeFeatureTypeCount(FeatureType featureType) {
656
        Long count = (Long) featureTypeCounts.get(featureType);
657
        if (count == null) {
658
            count = new Long(-1);
659
        } else {
660
            count = count - 1;
661
        }
662
        featureTypeCounts.put(featureType, count);
663
        return count;
664
    }
665

    
666
    private Long addFeatureTypeCount(FeatureType featureType) {
667
        Long count = (Long) featureTypeCounts.get(featureType);
668
        if (count == null) {
669
            count = new Long(1);
670
        } else {
671
            count = count + 1;
672
        }
673
        featureTypeCounts.put(featureType, count);
674
        return count;
675
    }
676

    
677
    @Override
678
    public void delete(Feature feature) throws DataException {
679
        Iterator it = this.featureIterators.get(feature);
680
        if (it != null) {
681
            it.remove();
682
            return;
683
        }
684
        feature.getStore().delete(feature);
685
    }
686

    
687
    @Override
688
    public void insert(EditableFeature feature) throws DataException {
689
        feature.getStore().insert(feature);
690
    }
691

    
692
    @Override
693
    public void update(EditableFeature feature) throws DataException {
694
        feature.getStore().update(feature);
695
    }
696

    
697
    /*
698
     * (non-Javadoc)
699
     *
700
     * @seeorg.gvsig.fmap.dal.feature.impl.DefaultFeatureReferenceSelection#
701
     * loadFromState(org.gvsig.tools.persistence.PersistentState)
702
     */
703
    @Override
704
    public void loadFromState(PersistentState state)
705
            throws PersistenceException {
706
        this.featureReferenceSelection.loadFromState(state);
707
    }
708

    
709
    protected void doDispose() throws BaseException {
710
        this.featureReferenceSelection.doDispose();
711
        featureTypeCounts.clear();
712
    }
713

    
714
    public static void registerPersistent() {
715
        PersistenceManager manager = ToolsLocator.getPersistenceManager();
716
        DynStruct definition = manager.addDefinition(
717
                DefaultFeatureSelection.class, "DefaultFeatureSelection",
718
                "DefaultFeatureSelection Persistent definition", null, null);
719

    
720
        definition.extend(manager.getDefinition(DefaultFeatureReferenceSelection.DYNCLASS_PERSISTENT_NAME));
721
        definition.addDynFieldMap("featureTypeCounts")
722
                .setClassOfItems(Long.class).setMandatory(false);
723

    
724
    }
725

    
726
    @Override
727
    public Object clone() throws CloneNotSupportedException {
728
        DefaultFeatureSelection clone = (DefaultFeatureSelection) super.clone();
729
        clone.featureTypeCounts = new HashMap(featureTypeCounts);
730
        return clone;
731
    }
732

    
733
}