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