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