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}