001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.datatransfer.data; 003 004import java.awt.datatransfer.DataFlavor; 005import java.io.Serializable; 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.Collections; 009import java.util.LinkedHashSet; 010import java.util.LinkedList; 011import java.util.Queue; 012import java.util.Set; 013 014import org.openstreetmap.josm.data.ProjectionBounds; 015import org.openstreetmap.josm.data.coor.EastNorth; 016import org.openstreetmap.josm.data.osm.NodeData; 017import org.openstreetmap.josm.data.osm.OsmPrimitive; 018import org.openstreetmap.josm.data.osm.PrimitiveData; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.Way; 021import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 022import org.openstreetmap.josm.tools.CompositeList; 023 024/** 025 * A list of primitives that are transferred. The list allows you to implicitly add primitives. 026 * It distinguishes between primitives that were directly added and implicitly added ones. 027 * @author Michael Zangl 028 * @since 10604 029 */ 030public final class PrimitiveTransferData implements Serializable { 031 private static final long serialVersionUID = 1L; 032 033 /** 034 * The data flavor used to represent this class. 035 */ 036 public static final DataFlavor DATA_FLAVOR = new DataFlavor(PrimitiveTransferData.class, "OSM Primitives"); 037 038 private static final class GetReferences implements ReferenceGetter { 039 @Override 040 public Collection<? extends OsmPrimitive> getReferredPrimitives(OsmPrimitive primitive) { 041 if (primitive instanceof Way) { 042 return ((Way) primitive).getNodes(); 043 } else if (primitive instanceof Relation) { 044 return ((Relation) primitive).getMemberPrimitivesList(); 045 } else { 046 return Collections.emptyList(); 047 } 048 } 049 } 050 051 @FunctionalInterface 052 private interface ReferenceGetter { 053 Collection<? extends OsmPrimitive> getReferredPrimitives(OsmPrimitive primitive); 054 } 055 056 private final ArrayList<PrimitiveData> direct; 057 private final ArrayList<PrimitiveData> referenced; 058 059 /** 060 * Create the new transfer data. 061 * @param primitives The primitives to transfer 062 * @param referencedGetter A function that allows to get the primitives referenced by the primitives variable. 063 * It will be queried recursively. 064 */ 065 private PrimitiveTransferData(Collection<? extends OsmPrimitive> primitives, ReferenceGetter referencedGetter) { 066 // convert to hash set first to remove duplicates 067 Set<OsmPrimitive> visited = new LinkedHashSet<>(primitives); 068 this.direct = new ArrayList<>(visited.size()); 069 070 this.referenced = new ArrayList<>(); 071 Queue<OsmPrimitive> toCheck = new LinkedList<>(); 072 for (OsmPrimitive p : visited) { 073 direct.add(p.save()); 074 toCheck.addAll(referencedGetter.getReferredPrimitives(p)); 075 } 076 while (!toCheck.isEmpty()) { 077 OsmPrimitive p = toCheck.poll(); 078 if (visited.add(p)) { 079 referenced.add(p.save()); 080 toCheck.addAll(referencedGetter.getReferredPrimitives(p)); 081 } 082 } 083 } 084 085 /** 086 * Gets all primitives directly added. 087 * @return The primitives 088 */ 089 public Collection<PrimitiveData> getDirectlyAdded() { 090 return Collections.unmodifiableList(direct); 091 } 092 093 /** 094 * Gets all primitives that were added because they were referenced. 095 * @return The primitives 096 */ 097 public Collection<PrimitiveData> getReferenced() { 098 return Collections.unmodifiableList(referenced); 099 } 100 101 /** 102 * Gets a List of all primitives added to this set. 103 * @return That list. 104 */ 105 public Collection<PrimitiveData> getAll() { 106 return new CompositeList<>(direct, referenced); 107 } 108 109 /** 110 * Creates a new {@link PrimitiveTransferData} object that only contains the primitives. 111 * @param primitives The primitives to contain. 112 * @return That set. 113 */ 114 public static PrimitiveTransferData getData(Collection<? extends OsmPrimitive> primitives) { 115 return new PrimitiveTransferData(primitives, primitive -> Collections.emptyList()); 116 } 117 118 /** 119 * Creates a new {@link PrimitiveTransferData} object that contains the primitives and all references. 120 * @param primitives The primitives to contain. 121 * @return That set. 122 */ 123 public static PrimitiveTransferData getDataWithReferences(Collection<? extends OsmPrimitive> primitives) { 124 return new PrimitiveTransferData(primitives, new GetReferences()); 125 } 126 127 /** 128 * Compute the center of all nodes. 129 * @return The center or null if this buffer has no location. 130 */ 131 public EastNorth getCenter() { 132 BoundingXYVisitor visitor = new BoundingXYVisitor(); 133 for (PrimitiveData pd : getAll()) { 134 if (pd instanceof NodeData && !pd.isIncomplete()) { 135 visitor.visit(((NodeData) pd)); 136 } 137 } 138 ProjectionBounds bounds = visitor.getBounds(); 139 if (bounds == null) { 140 return null; 141 } else { 142 return bounds.getCenter(); 143 } 144 } 145 146 /** 147 * Tests whether this set contains any primitives that have invalid data. 148 * @return <code>true</code> if invalid data is contained in this set. 149 */ 150 public boolean hasIncompleteData() { 151 return getAll().stream().anyMatch(p -> !p.isUsable()); 152 } 153}