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}