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}