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 *
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    public PrimitiveDeepCopy() {
029
030    }
031
032    public PrimitiveDeepCopy(final Collection<OsmPrimitive> primitives) {
033        makeCopy(primitives);
034    }
035
036    /**
037     * Replace content of the object with copy of provided primitives
038     * @param primitives
039     */
040    public final void makeCopy(final Collection<OsmPrimitive> primitives) {
041        directlyAdded.clear();
042        referenced.clear();
043
044        final Set<Long> visitedNodeIds = new HashSet<>();
045        final Set<Long> visitedWayIds = new HashSet<>();
046        final Set<Long> visitedRelationIds = new HashSet<>();
047
048        new AbstractVisitor() {
049            boolean firstIteration;
050
051            @Override
052            public void visit(Node n) {
053                if (!visitedNodeIds.add(n.getUniqueId()))
054                    return;
055                (firstIteration ? directlyAdded : referenced).add(n.save());
056            }
057            @Override
058            public void visit(Way w) {
059                if (!visitedWayIds.add(w.getUniqueId()))
060                    return;
061                (firstIteration ? directlyAdded : referenced).add(w.save());
062                firstIteration = false;
063                for (Node n : w.getNodes()) {
064                    visit(n);
065                }
066            }
067            @Override
068            public void visit(Relation r) {
069                if (!visitedRelationIds.add(r.getUniqueId()))
070                    return;
071                (firstIteration ? directlyAdded : referenced).add(r.save());
072                firstIteration = false;
073                for (RelationMember m : r.getMembers()) {
074                    m.getMember().accept(this);
075                }
076            }
077
078            public final void visitAll() {
079                for (OsmPrimitive osm : primitives) {
080                    firstIteration = true;
081                    osm.accept(this);
082                }
083            }
084        }.visitAll();
085
086        firePasteBufferChanged();
087    }
088
089    public List<PrimitiveData> getDirectlyAdded() {
090        return directlyAdded;
091    }
092
093    public List<PrimitiveData> getReferenced() {
094        return referenced;
095    }
096
097    public List<PrimitiveData> getAll() {
098        List<PrimitiveData> result = new ArrayList<>(directlyAdded.size() + referenced.size());
099        result.addAll(directlyAdded);
100        result.addAll(referenced);
101        return result;
102    }
103
104    public boolean isEmpty() {
105        return directlyAdded.isEmpty() && referenced.isEmpty();
106    }
107
108    private void firePasteBufferChanged() {
109        for (PasteBufferChangedListener listener: listeners) {
110            listener.pasteBufferChanged(this);
111        }
112    }
113
114    public void addPasteBufferChangedListener(PasteBufferChangedListener listener) {
115        listeners.addIfAbsent(listener);
116    }
117
118    public void removePasteBufferChangedListener(PasteBufferChangedListener listener) {
119        listeners.remove(listener);
120    }
121
122}