// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.datatransfer.data;
import java.awt.datatransfer.DataFlavor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import org.openstreetmap.josm.data.ProjectionBounds;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.osm.NodeData;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.PrimitiveData;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
import org.openstreetmap.josm.tools.CompositeList;
/**
* A list of primitives that are transfered. The list allows you to implicitly add primitives.
* It distinguishes between primitives that were directly added and implicitly added ones.
* @author Michael Zangl
* @since 10604
*/
public final class PrimitiveTransferData implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The data flavor used to represent this class.
*/
public static final DataFlavor DATA_FLAVOR = new DataFlavor(PrimitiveTransferData.class, "OSM Primitives");
private static final class GetReferences implements ReferenceGetter {
@Override
public Collection<? extends OsmPrimitive> getReferredPrimitives(OsmPrimitive primitive) {
if (primitive instanceof Way) {
return ((Way) primitive).getNodes();
} else if (primitive instanceof Relation) {
return ((Relation) primitive).getMemberPrimitivesList();
} else {
return Collections.emptyList();
}
}
}
@FunctionalInterface
private interface ReferenceGetter {
Collection<? extends OsmPrimitive> getReferredPrimitives(OsmPrimitive primitive);
}
private final ArrayList<PrimitiveData> direct;
private final ArrayList<PrimitiveData> referenced;
/**
* Create the new transfer data.
* @param primitives The primitives to transfer
* @param referencedGetter A function that allows to get the primitives referenced by the primitives variable.
* It will be queried recursively.
*/
private PrimitiveTransferData(Collection<? extends OsmPrimitive> primitives, ReferenceGetter referencedGetter) {
// convert to hash set first to remove duplicates
HashSet<OsmPrimitive> visited = new HashSet<>(primitives);
this.direct = new ArrayList<>(visited.size());
this.referenced = new ArrayList<>();
Queue<OsmPrimitive> toCheck = new LinkedList<>();
for (OsmPrimitive p : visited) {
direct.add(p.save());
toCheck.addAll(referencedGetter.getReferredPrimitives(p));
}
while (!toCheck.isEmpty()) {
OsmPrimitive p = toCheck.poll();
if (visited.add(p)) {
referenced.add(p.save());
toCheck.addAll(referencedGetter.getReferredPrimitives(p));
}
}
}
/**
* Gets all primitives directly added.
* @return The primitives
*/
public Collection<PrimitiveData> getDirectlyAdded() {
return Collections.unmodifiableList(direct);
}
/**
* Gets all primitives that were added because they were referenced.
* @return The primitives
*/
public Collection<PrimitiveData> getReferenced() {
return Collections.unmodifiableList(referenced);
}
/**
* Gets a List of all primitives added to this set.
* @return That list.
*/
public Collection<PrimitiveData> getAll() {
return new CompositeList<>(direct, referenced);
}
/**
* Creates a new {@link PrimitiveTransferData} object that only contains the primitives.
* @param primitives The primitives to contain.
* @return That set.
*/
public static PrimitiveTransferData getData(Collection<? extends OsmPrimitive> primitives) {
return new PrimitiveTransferData(primitives, primitive -> Collections.emptyList());
}
/**
* Creates a new {@link PrimitiveTransferData} object that contains the primitives and all references.
* @param primitives The primitives to contain.
* @return That set.
*/
public static PrimitiveTransferData getDataWithReferences(Collection<? extends OsmPrimitive> primitives) {
return new PrimitiveTransferData(primitives, new GetReferences());
}
/**
* Compute the center of all nodes.
* @return The center or null if this buffer has no location.
*/
public EastNorth getCenter() {
BoundingXYVisitor visitor = new BoundingXYVisitor();
for (PrimitiveData pd : getAll()) {
if (pd instanceof NodeData && !pd.isIncomplete()) {
visitor.visit(((NodeData) pd).getEastNorth());
}
}
ProjectionBounds bounds = visitor.getBounds();
if (bounds == null) {
return null;
} else {
return bounds.getCenter();
}
}
/**
* Tests wheter this set contains any primitives that have invalid data.
* @return <code>true</code> if invalid data is contained in this set.
*/
public boolean hasIncompleteData() {
return getAll().stream().anyMatch(p -> p.isIncomplete() || !p.isVisible());
}
}