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 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 @Override 067 public void visit(Way w) { 068 if (!visitedWayIds.add(w.getUniqueId())) 069 return; 070 (firstIteration ? directlyAdded : referenced).add(w.save()); 071 firstIteration = false; 072 for (Node n : w.getNodes()) { 073 visit(n); 074 } 075 } 076 @Override 077 public void visit(Relation r) { 078 if (!visitedRelationIds.add(r.getUniqueId())) 079 return; 080 (firstIteration ? directlyAdded : referenced).add(r.save()); 081 firstIteration = false; 082 for (RelationMember m : r.getMembers()) { 083 m.getMember().accept(this); 084 } 085 } 086 087 public final void visitAll() { 088 for (OsmPrimitive osm : primitives) { 089 firstIteration = true; 090 osm.accept(this); 091 } 092 } 093 }.visitAll(); 094 095 firePasteBufferChanged(); 096 } 097 098 public List<PrimitiveData> getDirectlyAdded() { 099 return directlyAdded; 100 } 101 102 public List<PrimitiveData> getReferenced() { 103 return referenced; 104 } 105 106 public List<PrimitiveData> getAll() { 107 List<PrimitiveData> result = new ArrayList<>(directlyAdded.size() + referenced.size()); 108 result.addAll(directlyAdded); 109 result.addAll(referenced); 110 return result; 111 } 112 113 public boolean isEmpty() { 114 return directlyAdded.isEmpty() && referenced.isEmpty(); 115 } 116 117 private void firePasteBufferChanged() { 118 for (PasteBufferChangedListener listener: listeners) { 119 listener.pasteBufferChanged(this); 120 } 121 } 122 123 public void addPasteBufferChangedListener(PasteBufferChangedListener listener) { 124 listeners.addIfAbsent(listener); 125 } 126 127 public void removePasteBufferChangedListener(PasteBufferChangedListener listener) { 128 listeners.remove(listener); 129 } 130}