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}