source: josm/trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java@ 6233

Last change on this file since 6233 was 6233, checked in by Don-vip, 11 years ago

fix #9084 - Add KSJ tags to discardable keys

  • Property svn:eol-style set to native
File size: 44.3 KB
RevLine 
[298]1// License: GPL. Copyright 2007 by Immanuel Scholz and others
[1]2package org.openstreetmap.josm.data.osm;
3
[1200]4import static org.openstreetmap.josm.tools.I18n.tr;
5
[2845]6import java.text.MessageFormat;
[1499]7import java.util.ArrayList;
[367]8import java.util.Arrays;
[66]9import java.util.Collection;
10import java.util.Collections;
[81]11import java.util.Date;
[2482]12import java.util.HashSet;
[2341]13import java.util.LinkedHashSet;
[2070]14import java.util.LinkedList;
15import java.util.List;
[1]16import java.util.Map;
[2482]17import java.util.Set;
[1]18
[1499]19import org.openstreetmap.josm.Main;
[2645]20import org.openstreetmap.josm.actions.search.SearchCompiler;
21import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
22import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
[8]23import org.openstreetmap.josm.data.osm.visitor.Visitor;
[3824]24import org.openstreetmap.josm.gui.mappaint.StyleCache;
[2845]25import org.openstreetmap.josm.tools.CheckParameterUtil;
[3177]26import org.openstreetmap.josm.tools.Predicate;
[4431]27import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
[1]28
[5754]29
[1]30/**
[5589]31 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
[6069]32 *
[5589]33 * It can be created, deleted and uploaded to the OSM-Server.
[58]34 *
35 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
[655]36 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
[795]37 * by the server environment and not an extendible data stuff.
[58]38 *
[1]39 * @author imi
40 */
[4431]41abstract public class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, TemplateEngineDataProvider {
42 private static final String SPECIAL_VALUE_ID = "id";
43 private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
[1]44
[4431]45
[3177]46 /**
47 * An object can be disabled by the filter mechanism.
[3300]48 * Then it will show in a shade of gray on the map or it is completely
49 * hidden from the view.
[3177]50 * Disabled objects usually cannot be selected or modified
51 * while the filter is active.
52 */
[4099]53 protected static final int FLAG_DISABLED = 1 << 4;
[3177]54
55 /**
[3300]56 * This flag is only relevant if an object is disabled by the
[5437]57 * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
[3300]58 * Then it indicates, whether it is completely hidden or
59 * just shown in gray color.
60 *
61 * When the primitive is not disabled, this flag should be
62 * unset as well (for efficient access).
[3177]63 */
[4099]64 protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5;
[3177]65
66 /**
[5437]67 * Flag used internally by the filter mechanism.
68 */
69 protected static final int FLAG_DISABLED_TYPE = 1 << 6;
70
71 /**
72 * Flag used internally by the filter mechanism.
73 */
74 protected static final int FLAG_HIDDEN_TYPE = 1 << 7;
75
76 /**
[3177]77 * This flag is set if the primitive is a way and
78 * according to the tags, the direction of the way is important.
79 * (e.g. one way street.)
80 */
[5437]81 protected static final int FLAG_HAS_DIRECTIONS = 1 << 8;
[3177]82
83 /**
84 * If the primitive is tagged.
85 * Some trivial tags like source=* are ignored here.
86 */
[5437]87 protected static final int FLAG_TAGGED = 1 << 9;
[3177]88
89 /**
90 * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
91 * It shows, that direction of the arrows should be reversed.
92 * (E.g. oneway=-1.)
93 */
[5437]94 protected static final int FLAG_DIRECTION_REVERSED = 1 << 10;
[3177]95
96 /**
97 * When hovering over ways and nodes in add mode, the
98 * "target" objects are visually highlighted. This flag indicates
99 * that the primitive is currently highlighted.
100 */
[5437]101 protected static final int FLAG_HIGHLIGHTED = 1 << 11;
[3177]102
103 /**
[5754]104 * If the primitive is annotated with a tag such as note, fixme, etc.
105 * Match the "work in progress" tags in default elemstyles.xml.
106 */
107 protected static final int FLAG_ANNOTATED = 1 << 12;
108
109 /**
[5266]110 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
111 * another collection of {@link OsmPrimitive}s. The result collection is a list.
[2206]112 *
[2188]113 * If <code>list</code> is null, replies an empty list.
[2206]114 *
[2188]115 * @param <T>
116 * @param list the original list
117 * @param type the type to filter for
118 * @return the sub-list of OSM primitives of type <code>type</code>
119 */
[2070]120 static public <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
[2188]121 if (list == null) return Collections.emptyList();
[2070]122 List<T> ret = new LinkedList<T>();
123 for(OsmPrimitive p: list) {
124 if (type.isInstance(p)) {
125 ret.add(type.cast(p));
126 }
127 }
128 return ret;
129 }
130
[2188]131 /**
[5266]132 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
133 * another collection of {@link OsmPrimitive}s. The result collection is a set.
[2206]134 *
[2188]135 * If <code>list</code> is null, replies an empty set.
[2206]136 *
[5608]137 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
138 * @param set the original collection
[2188]139 * @param type the type to filter for
140 * @return the sub-set of OSM primitives of type <code>type</code>
141 */
[2341]142 static public <T extends OsmPrimitive> LinkedHashSet<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
143 LinkedHashSet<T> ret = new LinkedHashSet<T>();
144 if (set != null) {
145 for(OsmPrimitive p: set) {
146 if (type.isInstance(p)) {
147 ret.add(type.cast(p));
148 }
[2070]149 }
150 }
151 return ret;
152 }
153
[2565]154 /**
155 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
[2711]156 *
[2565]157 * @param primitives the collection of primitives.
158 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
159 * empty set if primitives is null or if there are no referring primitives
160 */
161 static public Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
162 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
163 if (primitives == null || primitives.isEmpty()) return ret;
164 for (OsmPrimitive p: primitives) {
165 ret.addAll(p.getReferrers());
166 }
167 return ret;
168 }
169
[3342]170 /**
171 * Some predicates, that describe conditions on primitives.
172 */
173 public static final Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() {
[4098]174 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]175 return primitive.isUsable();
176 }
177 };
178
179 public static final Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
[4098]180 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]181 return primitive.isSelectable();
182 }
183 };
184
185 public static final Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() {
[4098]186 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]187 return !primitive.isDeleted();
188 }
189 };
190
191 public static final Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() {
[4098]192 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]193 return !primitive.isDeleted() && !primitive.isIncomplete();
194 }
195 };
196
197 public static final Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() {
[4098]198 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]199 return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation);
200 }
201 };
202
203 public static final Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() {
[4098]204 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]205 return primitive.isModified();
206 }
207 };
208
209 public static final Predicate<OsmPrimitive> nodePredicate = new Predicate<OsmPrimitive>() {
[4098]210 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]211 return primitive.getClass() == Node.class;
212 }
213 };
214
215 public static final Predicate<OsmPrimitive> wayPredicate = new Predicate<OsmPrimitive>() {
[4098]216 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]217 return primitive.getClass() == Way.class;
218 }
219 };
220
221 public static final Predicate<OsmPrimitive> relationPredicate = new Predicate<OsmPrimitive>() {
[4098]222 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]223 return primitive.getClass() == Relation.class;
224 }
225 };
226
[5442]227 public static final Predicate<OsmPrimitive> multipolygonPredicate = new Predicate<OsmPrimitive>() {
228 @Override public boolean evaluate(OsmPrimitive primitive) {
229 return primitive.getClass() == Relation.class && ((Relation) primitive).isMultipolygon();
230 }
231 };
232
[3342]233 public static final Predicate<OsmPrimitive> allPredicate = new Predicate<OsmPrimitive>() {
[4098]234 @Override public boolean evaluate(OsmPrimitive primitive) {
[3342]235 return true;
236 }
237 };
238
239 /**
240 * Creates a new primitive for the given id.
241 *
242 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
243 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
244 * positive number.
245 *
246 * @param id the id
247 * @param allowNegativeId
248 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
249 */
250 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
251 if (allowNegativeId) {
252 this.id = id;
253 } else {
254 if (id < 0)
255 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
256 else if (id == 0) {
257 this.id = generateUniqueId();
258 } else {
259 this.id = id;
260 }
261
262 }
263 this.version = 0;
264 this.setIncomplete(id > 0);
265 }
266
267 /**
268 * Creates a new primitive for the given id and version.
269 *
270 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
271 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
272 * positive number.
273 *
274 * If id is not > 0 version is ignored and set to 0.
275 *
276 * @param id
277 * @param version
278 * @param allowNegativeId
279 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
280 */
281 protected OsmPrimitive(long id, int version, boolean allowNegativeId) throws IllegalArgumentException {
282 this(id, allowNegativeId);
283 this.version = (id > 0 ? version : 0);
284 setIncomplete(id > 0 && version == 0);
285 }
286
287
288 /*----------
289 * MAPPAINT
290 *--------*/
[3824]291 public StyleCache mappaintStyle = null;
[3843]292 public int mappaintCacheIdx;
[1200]293
[1313]294 /* This should not be called from outside. Fixing the UI to add relevant
[1314]295 get/set functions calling this implicitely is preferred, so we can have
[1313]296 transparent cache handling in the future. */
[3943]297 public void clearCachedStyle()
[1313]298 {
299 mappaintStyle = null;
300 }
[1263]301 /* end of mappaint data */
302
[3342]303 /*---------
304 * DATASET
305 *---------*/
[23]306
[2481]307 /** the parent dataset */
[2405]308 private DataSet dataSet;
309
310 /**
311 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
312 * @param dataSet
313 */
314 void setDataset(DataSet dataSet) {
315 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
316 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
317 this.dataSet = dataSet;
318 }
319
320 /**
[2460]321 *
[2405]322 * @return DataSet this primitive is part of.
323 */
324 public DataSet getDataSet() {
325 return dataSet;
326 }
327
[2407]328 /**
329 * Throws exception if primitive is not part of the dataset
330 */
331 public void checkDataset() {
332 if (dataSet == null)
[4000]333 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
[2407]334 }
335
[3348]336 protected boolean writeLock() {
337 if (dataSet != null) {
338 dataSet.beginUpdate();
339 return true;
340 } else
341 return false;
342 }
343
344 protected void writeUnlock(boolean locked) {
345 if (locked) {
346 // It shouldn't be possible for dataset to become null because method calling setDataset would need write lock which is owned by this thread
347 dataSet.endUpdate();
348 }
349 }
350
[1169]351 /**
[3342]352 * Sets the id and the version of this primitive if it is known to the OSM API.
353 *
354 * Since we know the id and its version it can't be incomplete anymore. incomplete
355 * is set to false.
356 *
357 * @param id the id. > 0 required
358 * @param version the version > 0 required
359 * @throws IllegalArgumentException thrown if id <= 0
360 * @throws IllegalArgumentException thrown if version <= 0
361 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
362 */
[4098]363 @Override
[3342]364 public void setOsmId(long id, int version) {
[3348]365 boolean locked = writeLock();
366 try {
367 if (id <= 0)
368 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
369 if (version <= 0)
370 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
371 if (dataSet != null && id != this.id) {
372 DataSet datasetCopy = dataSet;
373 // Reindex primitive
374 datasetCopy.removePrimitive(this);
375 this.id = id;
376 datasetCopy.addPrimitive(this);
377 }
[4099]378 super.setOsmId(id, version);
[3348]379 } finally {
380 writeUnlock(locked);
[3342]381 }
382 }
[2284]383
[3342]384 /**
[6140]385 * Clears the metadata, including id and version known to the OSM API.
386 * The id is a new unique id. The version, changeset and timestamp are set to 0.
387 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
[3342]388 *
[5266]389 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
[3342]390 *
391 * @throws DataIntegrityProblemException If primitive was already added to the dataset
[6140]392 * @since 6140
[3342]393 */
[4099]394 @Override
[6140]395 public void clearOsmMetadata() {
[3342]396 if (dataSet != null)
397 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
[6140]398 super.clearOsmMetadata();
[2070]399 }
400
[4098]401 @Override
[3342]402 public void setUser(User user) {
[3348]403 boolean locked = writeLock();
404 try {
[4099]405 super.setUser(user);
[3348]406 } finally {
407 writeUnlock(locked);
408 }
[3342]409 }
410
[4098]411 @Override
[3342]412 public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
[3348]413 boolean locked = writeLock();
414 try {
415 int old = this.changesetId;
[4099]416 super.setChangesetId(changesetId);
[3348]417 if (dataSet != null) {
418 dataSet.fireChangesetIdChanged(this, old, changesetId);
419 }
420 } finally {
421 writeUnlock(locked);
[3342]422 }
423 }
[3300]424
[4098]425 @Override
[3342]426 public void setTimestamp(Date timestamp) {
[3348]427 boolean locked = writeLock();
428 try {
[4099]429 super.setTimestamp(timestamp);
[3348]430 } finally {
431 writeUnlock(locked);
432 }
[3342]433 }
434
435
436 /* -------
437 /* FLAGS
438 /* ------*/
439
[4099]440 private void updateFlagsNoLock (int flag, boolean value) {
441 super.updateFlags(flag, value);
[3342]442 }
[4431]443
[4099]444 @Override
445 protected final void updateFlags(int flag, boolean value) {
[3348]446 boolean locked = writeLock();
447 try {
448 updateFlagsNoLock(flag, value);
449 } finally {
450 writeUnlock(locked);
451 }
452 }
453
[3342]454 /**
[5423]455 * Make the primitive disabled (e.g.&nbsp;if a filter applies).
456 *
[3300]457 * To enable the primitive again, use unsetDisabledState.
[5437]458 * @param hidden if the primitive should be completely hidden from view or
[3300]459 * just shown in gray color.
[5423]460 * @return true, any flag has changed; false if you try to set the disabled
461 * state to the value that is already preset
[2120]462 */
[5437]463 public boolean setDisabledState(boolean hidden) {
[3348]464 boolean locked = writeLock();
465 try {
[3471]466 int oldFlags = flags;
[3348]467 updateFlagsNoLock(FLAG_DISABLED, true);
[5437]468 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
[3471]469 return oldFlags != flags;
[3348]470 } finally {
471 writeUnlock(locked);
[2206]472 }
[3300]473 }
[2206]474
[3300]475 /**
476 * Remove the disabled flag from the primitive.
477 * Afterwards, the primitive is displayed normally and can be selected
478 * again.
479 */
[3471]480 public boolean unsetDisabledState() {
481 boolean locked = writeLock();
482 try {
483 int oldFlags = flags;
484 updateFlagsNoLock(FLAG_DISABLED + FLAG_HIDE_IF_DISABLED, false);
485 return oldFlags != flags;
486 } finally {
487 writeUnlock(locked);
488 }
[2120]489 }
[2070]490
491 /**
[5437]492 * Set binary property used internally by the filter mechanism.
493 */
494 public void setDisabledType(boolean isExplicit) {
495 updateFlags(FLAG_DISABLED_TYPE, isExplicit);
496 }
497
498 /**
499 * Set binary property used internally by the filter mechanism.
500 */
501 public void setHiddenType(boolean isExplicit) {
502 updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
503 }
504
505 /**
[3300]506 * Replies true, if this primitive is disabled. (E.g. a filter
507 * applies)
[2120]508 */
509 public boolean isDisabled() {
[2206]510 return (flags & FLAG_DISABLED) != 0;
[2120]511 }
[3300]512
[2120]513 /**
[3300]514 * Replies true, if this primitive is disabled and marked as
515 * completely hidden on the map.
[2120]516 */
[3300]517 public boolean isDisabledAndHidden() {
518 return (((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0));
[2120]519 }
[3300]520
[5437]521 /**
522 * Get binary property used internally by the filter mechanism.
523 */
524 public boolean getHiddenType() {
525 return (flags & FLAG_HIDDEN_TYPE) != 0;
526 }
527
528 /**
529 * Get binary property used internally by the filter mechanism.
530 */
531 public boolean getDisabledType() {
532 return (flags & FLAG_DISABLED_TYPE) != 0;
533 }
534
[3177]535 public boolean isSelectable() {
[3300]536 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0;
[3177]537 }
538
[3129]539 public boolean isDrawable() {
[3300]540 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
[2603]541 }
542
[4098]543 @Override
[4099]544 public void setVisible(boolean visible) throws IllegalStateException {
[3348]545 boolean locked = writeLock();
546 try {
[4099]547 super.setVisible(visible);
[3348]548 } finally {
549 writeUnlock(locked);
550 }
[2025]551 }
552
[4098]553 @Override
[3342]554 public void setDeleted(boolean deleted) {
[3348]555 boolean locked = writeLock();
556 try {
[4099]557 super.setDeleted(deleted);
[3348]558 if (dataSet != null) {
559 if (deleted) {
560 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
561 } else {
562 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
563 }
[3342]564 }
[3348]565 } finally {
566 writeUnlock(locked);
[3342]567 }
[2284]568 }
569
[4099]570 @Override
571 protected void setIncomplete(boolean incomplete) {
[3348]572 boolean locked = writeLock();
573 try {
574 if (dataSet != null && incomplete != this.isIncomplete()) {
575 if (incomplete) {
576 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
577 } else {
578 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
579 }
[3342]580 }
[4099]581 super.setIncomplete(incomplete);
[3348]582 } finally {
583 writeUnlock(locked);
[3342]584 }
[2025]585 }
586
[3342]587 public boolean isSelected() {
588 return dataSet != null && dataSet.isSelected(this);
[3336]589 }
590
[3836]591 public boolean isMemberOfSelected() {
592 if (referrers == null)
593 return false;
594 if (referrers instanceof OsmPrimitive)
595 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
596 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
597 if (ref instanceof Relation && ref.isSelected())
598 return true;
599 }
600 return false;
601 }
602
[3342]603 public void setHighlighted(boolean highlighted) {
604 if (isHighlighted() != highlighted) {
605 updateFlags(FLAG_HIGHLIGHTED, highlighted);
606 if (dataSet != null) {
607 dataSet.fireHighlightingChanged(this);
608 }
[2415]609 }
[2070]610 }
[6]611
[3342]612 public boolean isHighlighted() {
613 return (flags & FLAG_HIGHLIGHTED) != 0;
[2070]614 }
[1499]615
[5754]616 /*---------------------------------------------------
617 * WORK IN PROGRESS, UNINTERESTING AND DIRECTION KEYS
618 *--------------------------------------------------*/
[1499]619
[5754]620 private static volatile Collection<String> workinprogress = null;
[2645]621 private static volatile Collection<String> uninteresting = null;
[5497]622 private static volatile Collection<String> discardable = null;
[6069]623
[1169]624 /**
[5754]625 * Returns a list of "uninteresting" keys that do not make an object
[3003]626 * "tagged". Entries that end with ':' are causing a whole namespace to be considered
627 * "uninteresting". Only the first level namespace is considered.
628 * Initialized by isUninterestingKey()
[5754]629 * @return The list of uninteresting keys.
[1169]630 */
[1499]631 public static Collection<String> getUninterestingKeys() {
[1530]632 if (uninteresting == null) {
[5608]633 LinkedList<String> l = new LinkedList<String>(Arrays.asList(
[5754]634 "source", "source_ref", "source:", "comment",
635 "converted_by", "watch", "watch:",
[5608]636 "description", "attribution"));
637 l.addAll(getDiscardableKeys());
[5754]638 l.addAll(getWorkInProgressKeys());
[5608]639 uninteresting = Main.pref.getCollection("tags.uninteresting", l);
[1499]640 }
641 return uninteresting;
642 }
[795]643
[3003]644 /**
[5497]645 * Returns a list of keys which have been deemed uninteresting to the point
646 * that they can be silently removed from data which is being edited.
[5754]647 * @return The list of discardable keys.
[5497]648 */
649 public static Collection<String> getDiscardableKeys() {
[5754]650 if (discardable == null) {
[5497]651 discardable = Main.pref.getCollection("tags.discardable",
[6233]652 Arrays.asList(
653 "created_by",
654 "geobase:datasetName",
655 "geobase:uuid",
656 "KSJ2:ADS",
657 "KSJ2:ARE",
658 "KSJ2:AdminArea",
659 "KSJ2:COP_label",
660 "KSJ2:DFD",
661 "KSJ2:INT",
662 "KSJ2:INT_label",
663 "KSJ2:LOC",
664 "KSJ2:LPN",
665 "KSJ2:OPC",
666 "KSJ2:PubFacAdmin",
667 "KSJ2:RAC",
668 "KSJ2:RAC_label",
669 "KSJ2:RIC",
670 "KSJ2:RIN",
671 "KSJ2:WSC",
672 "KSJ2:coordinate",
673 "KSJ2:curve_id",
674 "KSJ2:curve_type",
675 "KSJ2:filename",
676 "KSJ2:lake_id",
677 "KSJ2:lat",
678 "KSJ2:long",
679 "KSJ2:river_id",
680 "odbl",
681 "odbl:note",
682 "SK53_bulk:load",
683 "sub_sea:type",
684 "tiger:source",
685 "tiger:separated",
686 "tiger:tlid",
687 "tiger:upload_uuid",
688 "yh:LINE_NAME",
689 "yh:LINE_NUM",
690 "yh:STRUCTURE",
691 "yh:TOTYUMONO",
692 "yh:TYPE",
693 "yh:WIDTH_RANK"
694 ));
[5497]695 }
696 return discardable;
697 }
[6069]698
[5754]699 /**
700 * Returns a list of "work in progress" keys that do not make an object
701 * "tagged" but "annotated".
702 * @return The list of work in progress keys.
703 * @since 5754
704 */
705 public static Collection<String> getWorkInProgressKeys() {
706 if (workinprogress == null) {
707 workinprogress = Main.pref.getCollection("tags.workinprogress",
708 Arrays.asList("note", "fixme", "FIXME"));
709 }
710 return workinprogress;
711 }
[5497]712
713 /**
[5754]714 * Determines if key is considered "uninteresting".
715 * @param key The key to check
716 * @return true if key is considered "uninteresting".
[3003]717 */
718 public static boolean isUninterestingKey(String key) {
719 getUninterestingKeys();
720 if (uninteresting.contains(key))
721 return true;
722 int pos = key.indexOf(':');
723 if (pos > 0)
724 return uninteresting.contains(key.substring(0, pos + 1));
725 return false;
726 }
727
[2645]728 private static volatile Match directionKeys = null;
[2890]729 private static volatile Match reversedDirectionKeys = null;
[1499]730
[1169]731 /**
732 * Contains a list of direction-dependent keys that make an object
733 * direction dependent.
734 * Initialized by checkDirectionTagged()
735 */
[2890]736 static {
[5188]737 String reversedDirectionDefault = "oneway=\"-1\"";
[2906]738
[5188]739 String directionDefault = "oneway? | aerialway=* | "+
[4431]740 "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
741 "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
[5687]742 "junction=roundabout | (highway=motorway_link & -oneway=no)";
[2890]743
744 try {
745 reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
746 } catch (ParseError e) {
747 System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
748
749 try {
750 reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
751 } catch (ParseError e2) {
752 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
[2645]753 }
[2890]754 }
755 try {
756 directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
757 } catch (ParseError e) {
758 System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
[2645]759
760 try {
[2890]761 directionKeys = SearchCompiler.compile(directionDefault, false, false);
762 } catch (ParseError e2) {
763 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
[2645]764 }
[1499]765 }
766 }
[795]767
[3342]768 private void updateTagged() {
769 if (keys != null) {
770 for (String key: keySet()) {
771 if (!isUninterestingKey(key)) {
[3348]772 updateFlagsNoLock(FLAG_TAGGED, true);
[3342]773 return;
774 }
[2439]775 }
776 }
[3348]777 updateFlagsNoLock(FLAG_TAGGED, false);
[1169]778 }
[795]779
[5754]780 private void updateAnnotated() {
781 if (keys != null) {
782 for (String key: keySet()) {
783 if (getWorkInProgressKeys().contains(key)) {
784 updateFlagsNoLock(FLAG_ANNOTATED, true);
785 return;
786 }
787 }
788 }
789 updateFlagsNoLock(FLAG_ANNOTATED, false);
790 }
791
[1169]792 /**
[5754]793 * Determines if this object is considered "tagged". To be "tagged", an object
[3342]794 * must have one or more "interesting" tags. "created_by" and "source"
795 * are typically considered "uninteresting" and do not make an object
796 * "tagged".
[5754]797 * @return true if this object is considered "tagged"
[2291]798 */
[3342]799 public boolean isTagged() {
800 return (flags & FLAG_TAGGED) != 0;
[2291]801 }
[6069]802
[5754]803 /**
804 * Determines if this object is considered "annotated". To be "annotated", an object
805 * must have one or more "work in progress" tags, such as "note" or "fixme".
806 * @return true if this object is considered "annotated"
807 * @since 5754
808 */
809 public boolean isAnnotated() {
810 return (flags & FLAG_ANNOTATED) != 0;
811 }
[2291]812
[3342]813 private void updateDirectionFlags() {
814 boolean hasDirections = false;
815 boolean directionReversed = false;
816 if (reversedDirectionKeys.match(this)) {
817 hasDirections = true;
818 directionReversed = true;
819 }
820 if (directionKeys.match(this)) {
821 hasDirections = true;
822 }
[2291]823
[3348]824 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
825 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
[2604]826 }
827
828 /**
[3342]829 * true if this object has direction dependent tags (e.g. oneway)
[2604]830 */
[3342]831 public boolean hasDirectionKeys() {
832 return (flags & FLAG_HAS_DIRECTIONS) != 0;
[2604]833 }
834
[3342]835 public boolean reversedDirection() {
836 return (flags & FLAG_DIRECTION_REVERSED) != 0;
[1169]837 }
[28]838
[1843]839 /*------------
840 * Keys handling
841 ------------*/
[4431]842
[4098]843 @Override
[4099]844 public final void setKeys(Map<String, String> keys) {
[3348]845 boolean locked = writeLock();
846 try {
[4099]847 super.setKeys(keys);
[3348]848 } finally {
849 writeUnlock(locked);
[1924]850 }
851 }
[4431]852
[4098]853 @Override
[1169]854 public final void put(String key, String value) {
[3348]855 boolean locked = writeLock();
856 try {
[4099]857 super.put(key, value);
[3348]858 } finally {
859 writeUnlock(locked);
[1169]860 }
[4431]861 }
862
[4098]863 @Override
[1169]864 public final void remove(String key) {
[3348]865 boolean locked = writeLock();
866 try {
[4099]867 super.remove(key);
[3348]868 } finally {
869 writeUnlock(locked);
[1169]870 }
871 }
[66]872
[4098]873 @Override
[1843]874 public final void removeAll() {
[3348]875 boolean locked = writeLock();
876 try {
[4099]877 super.removeAll();
[3348]878 } finally {
879 writeUnlock(locked);
[2657]880 }
[4431]881 }
882
[4098]883 @Override
[5676]884 protected void keysChangedImpl(Map<String, String> originalKeys) {
[3943]885 clearCachedStyle();
[3953]886 if (dataSet != null) {
887 for (OsmPrimitive ref : getReferrers()) {
888 ref.clearCachedStyle();
889 }
890 }
[2890]891 updateDirectionFlags();
[2465]892 updateTagged();
[5754]893 updateAnnotated();
[2465]894 if (dataSet != null) {
[2657]895 dataSet.fireTagsChanged(this, originalKeys);
[2465]896 }
897 }
898
[2407]899 /*------------
900 * Referrers
901 ------------*/
902
903 private Object referrers;
904
[1169]905 /**
[2407]906 * Add new referrer. If referrer is already included then no action is taken
907 * @param referrer
908 */
909 protected void addReferrer(OsmPrimitive referrer) {
910 // Based on methods from josm-ng
911 if (referrers == null) {
912 referrers = referrer;
913 } else if (referrers instanceof OsmPrimitive) {
914 if (referrers != referrer) {
915 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
916 }
917 } else {
918 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
919 if (primitive == referrer)
920 return;
921 }
922 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
923 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
924 System.arraycopy(orig, 0, bigger, 0, orig.length);
925 bigger[orig.length] = referrer;
926 referrers = bigger;
927 }
928 }
929
930 /**
931 * Remove referrer. No action is taken if referrer is not registered
932 * @param referrer
933 */
934 protected void removeReferrer(OsmPrimitive referrer) {
935 // Based on methods from josm-ng
936 if (referrers instanceof OsmPrimitive) {
937 if (referrers == referrer) {
938 referrers = null;
939 }
940 } else if (referrers instanceof OsmPrimitive[]) {
941 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
942 int idx = -1;
943 for (int i=0; i<orig.length; i++) {
944 if (orig[i] == referrer) {
945 idx = i;
946 break;
947 }
948 }
949 if (idx == -1)
950 return;
951
952 if (orig.length == 2) {
953 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
954 } else { // downsize the array
955 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
956 System.arraycopy(orig, 0, smaller, 0, idx);
957 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
958 referrers = smaller;
959 }
960 }
961 }
962 /**
963 * Find primitives that reference this primitive. Returns only primitives that are included in the same
964 * dataset as this primitive. <br>
[2460]965 *
[2407]966 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
967 * not return wnew because it's not part of the dataset <br>
[2460]968 *
[2407]969 * <code>Way wnew = new Way(existingWay)</code>
[2460]970 *
[4725]971 * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false,
972 * exception will be thrown in this case
973 *
[2407]974 * @return a collection of all primitives that reference this primitive.
975 */
976
[4725]977 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
[2407]978 // Method copied from OsmPrimitive in josm-ng
979 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
980 // when way is cloned
[4725]981
982 if (dataSet == null && allowWithoutDataset)
983 return Collections.emptyList();
984
[3056]985 checkDataset();
[3348]986 Object referrers = this.referrers;
[2407]987 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
[2574]988 if (referrers != null) {
[2583]989 if (referrers instanceof OsmPrimitive) {
990 OsmPrimitive ref = (OsmPrimitive)referrers;
991 if (ref.dataSet == dataSet) {
992 result.add(ref);
993 }
[2574]994 } else {
995 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
996 if (dataSet == o.dataSet) {
997 result.add(o);
998 }
999 }
[2407]1000 }
1001 }
1002 return result;
1003 }
1004
[4725]1005 public final List<OsmPrimitive> getReferrers() {
1006 return getReferrers(false);
1007 }
1008
[3653]1009 /**
[4074]1010 * <p>Visits {@code visitor} for all referrers.</p>
[4431]1011 *
[4074]1012 * @param visitor the visitor. Ignored, if null.
1013 */
1014 public void visitReferrers(Visitor visitor){
1015 if (visitor == null) return;
1016 if (this.referrers == null)
1017 return;
1018 else if (this.referrers instanceof OsmPrimitive) {
1019 OsmPrimitive ref = (OsmPrimitive) this.referrers;
1020 if (ref.dataSet == dataSet) {
[6009]1021 ref.accept(visitor);
[4074]1022 }
1023 } else if (this.referrers instanceof OsmPrimitive[]) {
1024 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
1025 for (OsmPrimitive ref: refs) {
1026 if (ref.dataSet == dataSet) {
[6009]1027 ref.accept(visitor);
[4074]1028 }
1029 }
1030 }
1031 }
1032
1033 /**
[3653]1034 Return true, if this primitive is referred by at least n ways
1035 @param n Minimal number of ways to return true. Must be positive
1036 */
1037 public final boolean isReferredByWays(int n) {
1038 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
1039 // when way is cloned
1040 Object referrers = this.referrers;
1041 if (referrers == null) return false;
1042 checkDataset();
[4431]1043 if (referrers instanceof OsmPrimitive)
1044 return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet;
1045 else {
1046 int counter=0;
1047 for (OsmPrimitive o : (OsmPrimitive[])referrers) {
1048 if (dataSet == o.dataSet && o instanceof Way) {
1049 if (++counter >= n)
1050 return true;
1051 }
[3653]1052 }
[4431]1053 return false;
[3653]1054 }
1055 }
1056
1057
[3342]1058 /*-----------------
1059 * OTHER METHODS
[5608]1060 *----------------*/
[3342]1061
[2407]1062 /**
[3342]1063 * Implementation of the visitor scheme. Subclasses have to call the correct
1064 * visitor function.
1065 * @param visitor The visitor from which the visit() function must be called.
1066 */
[6009]1067 abstract public void accept(Visitor visitor);
[3342]1068
1069 /**
[1169]1070 * Get and write all attributes from the parameter. Does not fire any listener, so
1071 * use this only in the data initializing phase
1072 */
[2604]1073 public void cloneFrom(OsmPrimitive other) {
[3348]1074 // write lock is provided by subclasses
[2623]1075 if (id != other.id && dataSet != null)
1076 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
[4099]1077
1078 super.cloneFrom(other);
[3943]1079 clearCachedStyle();
[1169]1080 }
[86]1081
[1169]1082 /**
[2417]1083 * Merges the technical and semantical attributes from <code>other</code> onto this.
[2460]1084 *
[2417]1085 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1086 * have an assigend OSM id, the IDs have to be the same.
[2460]1087 *
[2417]1088 * @param other the other primitive. Must not be null.
1089 * @throws IllegalArgumentException thrown if other is null.
1090 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
[2448]1091 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
[2417]1092 */
1093 public void mergeFrom(OsmPrimitive other) {
[3348]1094 boolean locked = writeLock();
1095 try {
1096 CheckParameterUtil.ensureParameterNotNull(other, "other");
1097 if (other.isNew() ^ isNew())
1098 throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not"));
1099 if (! other.isNew() && other.getId() != id)
1100 throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
[2696]1101
[3348]1102 setKeys(other.getKeys());
1103 timestamp = other.timestamp;
1104 version = other.version;
1105 setIncomplete(other.isIncomplete());
1106 flags = other.flags;
1107 user= other.user;
1108 changesetId = other.changesetId;
1109 } finally {
1110 writeUnlock(locked);
1111 }
[2417]1112 }
1113
1114 /**
[1690]1115 * Replies true if this primitive and other are equal with respect to their
1116 * semantic attributes.
1117 * <ol>
1118 * <li>equal id</ol>
1119 * <li>both are complete or both are incomplete</li>
1120 * <li>both have the same tags</li>
1121 * </ol>
1122 * @param other
1123 * @return true if this primitive and other are equal with respect to their
1124 * semantic attributes.
1125 */
1126 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
[2284]1127 if (!isNew() && id != other.id)
[1690]1128 return false;
[4725]1129 // if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
[4684]1130 if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
[1690]1131 return false;
[2482]1132 // can't do an equals check on the internal keys array because it is not ordered
1133 //
1134 return hasSameTags(other);
[1690]1135 }
1136
1137 /**
1138 * Replies true if this primitive and other are equal with respect to their
1139 * technical attributes. The attributes:
1140 * <ol>
1141 * <li>deleted</ol>
1142 * <li>modified</ol>
1143 * <li>timestamp</ol>
1144 * <li>version</ol>
1145 * <li>visible</ol>
1146 * <li>user</ol>
1147 * </ol>
1148 * have to be equal
1149 * @param other the other primitive
1150 * @return true if this primitive and other are equal with respect to their
1151 * technical attributes
1152 */
1153 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1154 if (other == null) return false;
1155
1156 return
[4431]1157 isDeleted() == other.isDeleted()
1158 && isModified() == other.isModified()
1159 && timestamp == other.timestamp
1160 && version == other.version
1161 && isVisible() == other.isVisible()
1162 && (user == null ? other.user==null : user==other.user)
1163 && changesetId == other.changesetId;
[1690]1164 }
1165
1166 /**
[2284]1167 * Loads (clone) this primitive from provided PrimitiveData
[5608]1168 * @param data The object which should be cloned
[2284]1169 */
[2405]1170 public void load(PrimitiveData data) {
[3348]1171 // Write lock is provided by subclasses
[2284]1172 setKeys(data.getKeys());
[2932]1173 setTimestamp(data.getTimestamp());
[2284]1174 user = data.getUser();
[2655]1175 setChangesetId(data.getChangesetId());
[2284]1176 setDeleted(data.isDeleted());
1177 setModified(data.isModified());
[2615]1178 setIncomplete(data.isIncomplete());
[3004]1179 version = data.getVersion();
[2284]1180 }
1181
1182 /**
1183 * Save parameters of this primitive to the transport object
[5608]1184 * @return The saved object data
[2284]1185 */
1186 public abstract PrimitiveData save();
1187
[5608]1188 /**
1189 * Save common parameters of primitives to the transport object
1190 * @param data The object to save the data into
1191 */
[2284]1192 protected void saveCommonAttributes(PrimitiveData data) {
[2305]1193 data.setId(id);
[4103]1194 data.setKeys(getKeys());
[2932]1195 data.setTimestamp(getTimestamp());
[2284]1196 data.setUser(user);
1197 data.setDeleted(isDeleted());
1198 data.setModified(isModified());
1199 data.setVisible(isVisible());
[2615]1200 data.setIncomplete(isIncomplete());
[2604]1201 data.setChangesetId(changesetId);
[3004]1202 data.setVersion(version);
[2284]1203 }
1204
[5608]1205 /**
1206 * Fetch the bounding box of the primitive
1207 * @return Bounding box of the object
1208 */
[3342]1209 public abstract BBox getBBox();
1210
1211 /**
1212 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1213 */
1214 public abstract void updatePosition();
1215
1216 /*----------------
1217 * OBJECT METHODS
1218 *---------------*/
1219
[4099]1220 @Override
[2363]1221 protected String getFlagsAsString() {
[4099]1222 StringBuilder builder = new StringBuilder(super.getFlagsAsString());
[2363]1223
[2890]1224 if (isDisabled()) {
[3398]1225 if (isDisabledAndHidden()) {
1226 builder.append("h");
1227 } else {
1228 builder.append("d");
1229 }
[2363]1230 }
[2890]1231 if (isTagged()) {
1232 builder.append("T");
1233 }
1234 if (hasDirectionKeys()) {
1235 if (reversedDirection()) {
1236 builder.append("<");
1237 } else {
1238 builder.append(">");
1239 }
1240 }
[2363]1241 return builder.toString();
1242 }
1243
[2437]1244 /**
[3342]1245 * Equal, if the id (and class) is equal.
[2460]1246 *
[3342]1247 * An primitive is equal to its incomplete counter part.
[2448]1248 */
[3342]1249 @Override public boolean equals(Object obj) {
1250 if (obj instanceof OsmPrimitive)
1251 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
1252 return false;
[2448]1253 }
[2578]1254
[3129]1255 /**
[3342]1256 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1257 *
1258 * An primitive has the same hashcode as its incomplete counterpart.
[3129]1259 */
[3342]1260 @Override public final int hashCode() {
1261 return (int)id;
[2578]1262 }
1263
[4431]1264 /**
1265 * Replies the display name of a primitive formatted by <code>formatter</code>
1266 *
1267 * @return the display name
1268 */
1269 public abstract String getDisplayName(NameFormatter formatter);
1270
1271 @Override
1272 public Collection<String> getTemplateKeys() {
1273 Collection<String> keySet = keySet();
1274 List<String> result = new ArrayList<String>(keySet.size() + 2);
1275 result.add(SPECIAL_VALUE_ID);
1276 result.add(SPECIAL_VALUE_LOCAL_NAME);
1277 result.addAll(keySet);
1278 return result;
1279 }
1280
1281 @Override
1282 public Object getTemplateValue(String name, boolean special) {
1283 if (special) {
1284 String lc = name.toLowerCase();
1285 if (SPECIAL_VALUE_ID.equals(lc))
1286 return getId();
1287 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1288 return getLocalName();
1289 else
1290 return null;
1291
1292 } else
1293 return getIgnoreCase(name);
1294 }
1295
1296 @Override
1297 public boolean evaluateCondition(Match condition) {
1298 return condition.match(this);
1299 }
1300
[5132]1301 /**
1302 * Replies the set of referring relations
1303 *
1304 * @return the set of referring relations
1305 */
1306 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1307 HashSet<Relation> ret = new HashSet<Relation>();
1308 for (OsmPrimitive w : primitives) {
1309 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1310 }
1311 return ret;
1312 }
[2264]1313}
Note: See TracBrowser for help on using the repository browser.