001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Set;
009import java.util.concurrent.CopyOnWriteArrayList;
010
011import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
012
013/**
014 * This class allows to create and keep a deep copy of primitives. Provides methods to access directly added
015 * primitives and reference primitives
016 * @since 2305
017 */
018public class PrimitiveDeepCopy {
019
020    public interface PasteBufferChangedListener {
021        void pasteBufferChanged(PrimitiveDeepCopy pasteBuffer);
022    }
023
024    private final List<PrimitiveData> directlyAdded = new ArrayList<>();
025    private final List<PrimitiveData> referenced = new ArrayList<>();
026    private final CopyOnWriteArrayList<PasteBufferChangedListener> listeners = new CopyOnWriteArrayList<>();
027
028    /**
029     * Constructs a new {@code PrimitiveDeepCopy} without data. Use {@link #makeCopy(Collection)} after that.
030     */
031    public PrimitiveDeepCopy() {
032        // Do nothing
033    }
034
035    /**
036     * Constructs a new {@code PrimitiveDeepCopy} of given OSM primitives.
037     * @param primitives OSM primitives to copy
038     * @since 7961
039     */
040    public PrimitiveDeepCopy(final Collection<? extends OsmPrimitive> primitives) {
041        makeCopy(primitives);
042    }
043
044    /**
045     * Replace content of the object with copy of provided primitives.
046     * @param primitives OSM primitives to copy
047     * @since 7961
048     */
049    public final void makeCopy(final Collection<? extends OsmPrimitive> primitives) {
050        directlyAdded.clear();
051        referenced.clear();
052
053        final Set<Long> visitedNodeIds = new HashSet<>();
054        final Set<Long> visitedWayIds = new HashSet<>();
055        final Set<Long> visitedRelationIds = new HashSet<>();
056
057        new AbstractVisitor() {
058            private boolean firstIteration;
059
060            @Override
061            public void visit(Node n) {
062                if (!visitedNodeIds.add(n.getUniqueId()))
063                    return;
064                (firstIteration ? directlyAdded : referenced).add(n.save());
065            }
066
067            @Override
068            public void visit(Way w) {
069                if (!visitedWayIds.add(w.getUniqueId()))
070                    return;
071                (firstIteration ? directlyAdded : referenced).add(w.save());
072                firstIteration = false;
073                for (Node n : w.getNodes()) {
074                    visit(n);
075                }
076            }
077
078            @Override
079            public void visit(Relation r) {
080                if (!visitedRelationIds.add(r.getUniqueId()))
081                    return;
082                (firstIteration ? directlyAdded : referenced).add(r.save());
083                firstIteration = false;
084                for (RelationMember m : r.getMembers()) {
085                    m.getMember().accept(this);
086                }
087            }
088
089            public void visitAll() {
090                for (OsmPrimitive osm : primitives) {
091                    firstIteration = true;
092                    osm.accept(this);
093                }
094            }
095        }.visitAll();
096
097        firePasteBufferChanged();
098    }
099
100    public List<PrimitiveData> getDirectlyAdded() {
101        return directlyAdded;
102    }
103
104    public List<PrimitiveData> getReferenced() {
105        return referenced;
106    }
107
108    public List<PrimitiveData> getAll() {
109        List<PrimitiveData> result = new ArrayList<>(directlyAdded.size() + referenced.size());
110        result.addAll(directlyAdded);
111        result.addAll(referenced);
112        return result;
113    }
114
115    public boolean isEmpty() {
116        return directlyAdded.isEmpty() && referenced.isEmpty();
117    }
118
119    private void firePasteBufferChanged() {
120        for (PasteBufferChangedListener listener: listeners) {
121            listener.pasteBufferChanged(this);
122        }
123    }
124
125    public void addPasteBufferChangedListener(PasteBufferChangedListener listener) {
126        listeners.addIfAbsent(listener);
127    }
128
129    public void removePasteBufferChangedListener(PasteBufferChangedListener listener) {
130        listeners.remove(listener);
131    }
132}