001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.vector;
003
004import java.util.Arrays;
005import java.util.List;
006import java.util.Map;
007import java.util.function.Consumer;
008import java.util.stream.Collectors;
009import java.util.stream.IntStream;
010import java.util.stream.Stream;
011
012import org.openstreetmap.josm.data.osm.AbstractPrimitive;
013import org.openstreetmap.josm.data.osm.IPrimitive;
014import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
015import org.openstreetmap.josm.gui.mappaint.StyleCache;
016import org.openstreetmap.josm.tools.Utils;
017
018/**
019 * The base class for Vector primitives
020 * @author Taylor Smock
021 * @since 17862
022 */
023public abstract class VectorPrimitive extends AbstractPrimitive implements DataLayer<String> {
024    private VectorDataSet dataSet;
025    private boolean highlighted;
026    private StyleCache mappaintStyle;
027    private final String layer;
028
029    /**
030     * Create a primitive for a specific vector layer
031     * @param layer The layer for the primitive
032     */
033    protected VectorPrimitive(String layer) {
034        this.layer = layer;
035        this.id = getIdGenerator().generateUniqueId();
036    }
037
038    @Override
039    protected void keysChangedImpl(Map<String, String> originalKeys) {
040        clearCachedStyle();
041        if (dataSet != null) {
042            for (IPrimitive ref : getReferrers()) {
043                ref.clearCachedStyle();
044            }
045        }
046    }
047
048    @Override
049    public boolean isHighlighted() {
050        return this.highlighted;
051    }
052
053    @Override
054    public void setHighlighted(boolean highlighted) {
055        this.highlighted = highlighted;
056    }
057
058    @Override
059    public boolean isTagged() {
060        return !this.getInterestingTags().isEmpty();
061    }
062
063    @Override
064    public boolean isAnnotated() {
065        return this.getInterestingTags().size() - this.getKeys().size() > 0;
066    }
067
068    @Override
069    public VectorDataSet getDataSet() {
070        return dataSet;
071    }
072
073    protected void setDataSet(VectorDataSet newDataSet) {
074        dataSet = newDataSet;
075    }
076
077    /*----------
078     * MAPPAINT
079     *--------*/
080
081    @Override
082    public final StyleCache getCachedStyle() {
083        return mappaintStyle;
084    }
085
086    @Override
087    public final void setCachedStyle(StyleCache mappaintStyle) {
088        this.mappaintStyle = mappaintStyle;
089    }
090
091    @Override
092    public final boolean isCachedStyleUpToDate() {
093        return mappaintStyle != null && mappaintCacheIdx == dataSet.getMappaintCacheIndex();
094    }
095
096    @Override
097    public final void declareCachedStyleUpToDate() {
098        this.mappaintCacheIdx = dataSet.getMappaintCacheIndex();
099    }
100
101    @Override
102    public boolean hasDirectionKeys() {
103        return false;
104    }
105
106    @Override
107    public boolean reversedDirection() {
108        return false;
109    }
110
111    /*------------
112     * Referrers
113     ------------*/
114    // Largely the same as OsmPrimitive, OsmPrimitive not modified at this time to avoid breaking binary compatibility
115
116    private Object referrers;
117
118    @Override
119    public final List<VectorPrimitive> getReferrers(boolean allowWithoutDataset) {
120        return referrers(allowWithoutDataset, VectorPrimitive.class)
121          .collect(Collectors.toList());
122    }
123
124    /**
125     * Add new referrer. If referrer is already included then no action is taken
126     * @param referrer The referrer to add
127     */
128    protected void addReferrer(IPrimitive referrer) {
129        if (referrers == null) {
130            referrers = referrer;
131        } else if (referrers instanceof IPrimitive) {
132            if (referrers != referrer) {
133                referrers = new IPrimitive[] {(IPrimitive) referrers, referrer};
134            }
135        } else {
136            for (IPrimitive primitive:(IPrimitive[]) referrers) {
137                if (primitive == referrer)
138                    return;
139            }
140            referrers = Utils.addInArrayCopy((IPrimitive[]) referrers, referrer);
141        }
142    }
143
144    /**
145     * Remove referrer. No action is taken if referrer is not registered
146     * @param referrer The referrer to remove
147     */
148    protected void removeReferrer(IPrimitive referrer) {
149        if (referrers instanceof IPrimitive) {
150            if (referrers == referrer) {
151                referrers = null;
152            }
153        } else if (referrers instanceof IPrimitive[]) {
154            IPrimitive[] orig = (IPrimitive[]) referrers;
155            int idx = IntStream.range(0, orig.length)
156              .filter(i -> orig[i] == referrer)
157              .findFirst().orElse(-1);
158            if (idx == -1)
159                return;
160
161            if (orig.length == 2) {
162                referrers = orig[1-idx]; // idx is either 0 or 1, take the other
163            } else { // downsize the array
164                IPrimitive[] smaller = new IPrimitive[orig.length-1];
165                System.arraycopy(orig, 0, smaller, 0, idx);
166                System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
167                referrers = smaller;
168            }
169        }
170    }
171
172    private <T extends IPrimitive> Stream<T> referrers(boolean allowWithoutDataset, Class<T> filter) {
173        // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
174        // when way is cloned
175
176        if (dataSet == null && !allowWithoutDataset) {
177            return Stream.empty();
178        }
179        if (referrers == null) {
180            return Stream.empty();
181        }
182        final Stream<IPrimitive> stream = referrers instanceof IPrimitive // NOPMD
183          ? Stream.of((IPrimitive) referrers)
184          : Arrays.stream((IPrimitive[]) referrers);
185        return stream
186          .filter(p -> p.getDataSet() == dataSet)
187          .filter(filter::isInstance)
188          .map(filter::cast);
189    }
190
191    /**
192     * Gets all primitives in the current dataset that reference this primitive.
193     * @param filter restrict primitives to subclasses
194     * @param <T> type of primitives
195     * @return the referrers as Stream
196     */
197    public final <T extends IPrimitive> Stream<T> referrers(Class<T> filter) {
198        return referrers(false, filter);
199    }
200
201    @Override
202    public void visitReferrers(PrimitiveVisitor visitor) {
203        if (visitor != null)
204            doVisitReferrers(o -> o.accept(visitor));
205    }
206
207    private void doVisitReferrers(Consumer<IPrimitive> visitor) {
208        if (this.referrers instanceof IPrimitive) {
209            IPrimitive ref = (IPrimitive) this.referrers;
210            if (ref.getDataSet() == dataSet) {
211                visitor.accept(ref);
212            }
213        } else if (this.referrers instanceof IPrimitive[]) {
214            IPrimitive[] refs = (IPrimitive[]) this.referrers;
215            for (IPrimitive ref: refs) {
216                if (ref.getDataSet() == dataSet) {
217                    visitor.accept(ref);
218                }
219            }
220        }
221    }
222
223    /**
224     * Set the id of the object
225     * @param id The id
226     */
227    protected void setId(long id) {
228        this.id = id;
229    }
230
231    /**
232     * Make this object disabled
233     * @param disabled {@code true} to disable the object
234     */
235    public void setDisabled(boolean disabled) {
236        this.updateFlags(FLAG_DISABLED, disabled);
237    }
238
239    /**
240     * Make this object visible
241     * @param visible {@code true} to make this object visible (default)
242     */
243    @Override
244    public void setVisible(boolean visible) {
245        this.updateFlags(FLAG_VISIBLE, visible);
246    }
247
248    /**************************
249     * Data layer information *
250     **************************/
251    @Override
252    public String getLayer() {
253        return this.layer;
254    }
255
256    @Override
257    public boolean isDrawable() {
258        return super.isDrawable() && this.isVisible();
259    }
260}