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    
612        private static volatile Collection<String> uninteresting = null;
613        /**
614         * Contains a list of "uninteresting" keys that do not make an object
615         * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
616         * "uninteresting".  Only the first level namespace is considered.
617         * Initialized by isUninterestingKey()
618         */
619        public static Collection<String> getUninterestingKeys() {
620            if (uninteresting == null) {
621                uninteresting = Main.pref.getCollection("tags.uninteresting",
622                        Arrays.asList(new String[]{"source", "source_ref", "source:", "note", "comment",
623                                "converted_by", "created_by", "watch", "watch:", "fixme", "FIXME",
624                                "description", "attribution"}));
625            }
626            return uninteresting;
627        }
628    
629        /**
630         * Returns true if key is considered "uninteresting".
631         */
632        public static boolean isUninterestingKey(String key) {
633            getUninterestingKeys();
634            if (uninteresting.contains(key))
635                return true;
636            int pos = key.indexOf(':');
637            if (pos > 0)
638                return uninteresting.contains(key.substring(0, pos + 1));
639            return false;
640        }
641    
642        private static volatile Match directionKeys = null;
643        private static volatile Match reversedDirectionKeys = null;
644    
645        /**
646         * Contains a list of direction-dependent keys that make an object
647         * direction dependent.
648         * Initialized by checkDirectionTagged()
649         */
650        static {
651            String reversedDirectionDefault = "oneway=\"-1\"";
652    
653            String directionDefault = "oneway? | aerialway=* | "+
654                    "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
655                    "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
656                    "junction=roundabout";
657    
658            try {
659                reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
660            } catch (ParseError e) {
661                System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
662    
663                try {
664                    reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
665                } catch (ParseError e2) {
666                    throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
667                }
668            }
669            try {
670                directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
671            } catch (ParseError e) {
672                System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
673    
674                try {
675                    directionKeys = SearchCompiler.compile(directionDefault, false, false);
676                } catch (ParseError e2) {
677                    throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
678                }
679            }
680        }
681    
682        private void updateTagged() {
683            if (keys != null) {
684                for (String key: keySet()) {
685                    if (!isUninterestingKey(key)) {
686                        updateFlagsNoLock(FLAG_TAGGED, true);
687                        return;
688                    }
689                }
690            }
691            updateFlagsNoLock(FLAG_TAGGED, false);
692        }
693    
694        /**
695         * true if this object is considered "tagged". To be "tagged", an object
696         * must have one or more "interesting" tags. "created_by" and "source"
697         * are typically considered "uninteresting" and do not make an object
698         * "tagged".
699         */
700        public boolean isTagged() {
701            return (flags & FLAG_TAGGED) != 0;
702        }
703    
704        private void updateDirectionFlags() {
705            boolean hasDirections = false;
706            boolean directionReversed = false;
707            if (reversedDirectionKeys.match(this)) {
708                hasDirections = true;
709                directionReversed = true;
710            }
711            if (directionKeys.match(this)) {
712                hasDirections = true;
713            }
714    
715            updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
716            updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
717        }
718    
719        /**
720         * true if this object has direction dependent tags (e.g. oneway)
721         */
722        public boolean hasDirectionKeys() {
723            return (flags & FLAG_HAS_DIRECTIONS) != 0;
724        }
725    
726        public boolean reversedDirection() {
727            return (flags & FLAG_DIRECTION_REVERSED) != 0;
728        }
729    
730        /*------------
731         * Keys handling
732         ------------*/
733    
734        @Override
735        public final void setKeys(Map<String, String> keys) {
736            boolean locked = writeLock();
737            try {
738                super.setKeys(keys);
739            } finally {
740                writeUnlock(locked);
741            }
742        }
743    
744        @Override
745        public final void put(String key, String value) {
746            boolean locked = writeLock();
747            try {
748                super.put(key, value);
749            } finally {
750                writeUnlock(locked);
751            }
752        }
753    
754        @Override
755        public final void remove(String key) {
756            boolean locked = writeLock();
757            try {
758                super.remove(key);
759            } finally {
760                writeUnlock(locked);
761            }
762        }
763    
764        @Override
765        public final void removeAll() {
766            boolean locked = writeLock();
767            try {
768                super.removeAll();
769            } finally {
770                writeUnlock(locked);
771            }
772        }
773    
774        @Override
775        protected final void keysChangedImpl(Map<String, String> originalKeys) {
776            clearCachedStyle();
777            if (dataSet != null) {
778                for (OsmPrimitive ref : getReferrers()) {
779                    ref.clearCachedStyle();
780                }
781            }
782            updateDirectionFlags();
783            updateTagged();
784            if (dataSet != null) {
785                dataSet.fireTagsChanged(this, originalKeys);
786            }
787        }
788    
789        /*------------
790         * Referrers
791         ------------*/
792    
793        private Object referrers;
794    
795        /**
796         * Add new referrer. If referrer is already included then no action is taken
797         * @param referrer
798         */
799        protected void addReferrer(OsmPrimitive referrer) {
800            // Based on methods from josm-ng
801            if (referrers == null) {
802                referrers = referrer;
803            } else if (referrers instanceof OsmPrimitive) {
804                if (referrers != referrer) {
805                    referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
806                }
807            } else {
808                for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
809                    if (primitive == referrer)
810                        return;
811                }
812                OsmPrimitive[] orig = (OsmPrimitive[])referrers;
813                OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
814                System.arraycopy(orig, 0, bigger, 0, orig.length);
815                bigger[orig.length] = referrer;
816                referrers = bigger;
817            }
818        }
819    
820        /**
821         * Remove referrer. No action is taken if referrer is not registered
822         * @param referrer
823         */
824        protected void removeReferrer(OsmPrimitive referrer) {
825            // Based on methods from josm-ng
826            if (referrers instanceof OsmPrimitive) {
827                if (referrers == referrer) {
828                    referrers = null;
829                }
830            } else if (referrers instanceof OsmPrimitive[]) {
831                OsmPrimitive[] orig = (OsmPrimitive[])referrers;
832                int idx = -1;
833                for (int i=0; i<orig.length; i++) {
834                    if (orig[i] == referrer) {
835                        idx = i;
836                        break;
837                    }
838                }
839                if (idx == -1)
840                    return;
841    
842                if (orig.length == 2) {
843                    referrers = orig[1-idx]; // idx is either 0 or 1, take the other
844                } else { // downsize the array
845                    OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
846                    System.arraycopy(orig, 0, smaller, 0, idx);
847                    System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
848                    referrers = smaller;
849                }
850            }
851        }
852        /**
853         * Find primitives that reference this primitive. Returns only primitives that are included in the same
854         * dataset as this primitive. <br>
855         *
856         * For example following code will add wnew as referer to all nodes of existingWay, but this method will
857         * not return wnew because it's not part of the dataset <br>
858         *
859         * <code>Way wnew = new Way(existingWay)</code>
860         *
861         * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false,
862         * exception will be thrown in this case
863         *
864         * @return a collection of all primitives that reference this primitive.
865         */
866    
867        public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
868            // Method copied from OsmPrimitive in josm-ng
869            // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
870            // when way is cloned
871    
872            if (dataSet == null && allowWithoutDataset)
873                return Collections.emptyList();
874    
875            checkDataset();
876            Object referrers = this.referrers;
877            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
878            if (referrers != null) {
879                if (referrers instanceof OsmPrimitive) {
880                    OsmPrimitive ref = (OsmPrimitive)referrers;
881                    if (ref.dataSet == dataSet) {
882                        result.add(ref);
883                    }
884                } else {
885                    for (OsmPrimitive o:(OsmPrimitive[])referrers) {
886                        if (dataSet == o.dataSet) {
887                            result.add(o);
888                        }
889                    }
890                }
891            }
892            return result;
893        }
894    
895        public final List<OsmPrimitive> getReferrers() {
896            return getReferrers(false);
897        }
898    
899        /**
900         * <p>Visits {@code visitor} for all referrers.</p>
901         *
902         * @param visitor the visitor. Ignored, if null.
903         */
904        public void visitReferrers(Visitor visitor){
905            if (visitor == null) return;
906            if (this.referrers == null)
907                return;
908            else if (this.referrers instanceof OsmPrimitive) {
909                OsmPrimitive ref = (OsmPrimitive) this.referrers;
910                if (ref.dataSet == dataSet) {
911                    ref.visit(visitor);
912                }
913            } else if (this.referrers instanceof OsmPrimitive[]) {
914                OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
915                for (OsmPrimitive ref: refs) {
916                    if (ref.dataSet == dataSet) {
917                        ref.visit(visitor);
918                    }
919                }
920            }
921        }
922    
923        /**
924          Return true, if this primitive is referred by at least n ways
925          @param n Minimal number of ways to return true. Must be positive
926         */
927        public final boolean isReferredByWays(int n) {
928            // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
929            // when way is cloned
930            Object referrers = this.referrers;
931            if (referrers == null) return false;
932            checkDataset();
933            if (referrers instanceof OsmPrimitive)
934                return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet;
935            else {
936                int counter=0;
937                for (OsmPrimitive o : (OsmPrimitive[])referrers) {
938                    if (dataSet == o.dataSet && o instanceof Way) {
939                        if (++counter >= n)
940                            return true;
941                    }
942                }
943                return false;
944            }
945        }
946    
947    
948        /*-----------------
949         * OTHER METHODS
950         *----------------/
951    
952        /**
953         * Implementation of the visitor scheme. Subclasses have to call the correct
954         * visitor function.
955         * @param visitor The visitor from which the visit() function must be called.
956         */
957        abstract public void visit(Visitor visitor);
958    
959        /**
960         * Get and write all attributes from the parameter. Does not fire any listener, so
961         * use this only in the data initializing phase
962         */
963        public void cloneFrom(OsmPrimitive other) {
964            // write lock is provided by subclasses
965            if (id != other.id && dataSet != null)
966                throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
967    
968            super.cloneFrom(other);
969            clearCachedStyle();
970        }
971    
972        /**
973         * Merges the technical and semantical attributes from <code>other</code> onto this.
974         *
975         * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
976         * have an assigend OSM id, the IDs have to be the same.
977         *
978         * @param other the other primitive. Must not be null.
979         * @throws IllegalArgumentException thrown if other is null.
980         * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
981         * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
982         */
983        public void mergeFrom(OsmPrimitive other) {
984            boolean locked = writeLock();
985            try {
986                CheckParameterUtil.ensureParameterNotNull(other, "other");
987                if (other.isNew() ^ isNew())
988                    throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not"));
989                if (! other.isNew() && other.getId() != id)
990                    throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
991    
992                setKeys(other.getKeys());
993                timestamp = other.timestamp;
994                version = other.version;
995                setIncomplete(other.isIncomplete());
996                flags = other.flags;
997                user= other.user;
998                changesetId = other.changesetId;
999            } finally {
1000                writeUnlock(locked);
1001            }
1002        }
1003    
1004        /**
1005         * Replies true if this primitive and other are equal with respect to their
1006         * semantic attributes.
1007         * <ol>
1008         *   <li>equal id</ol>
1009         *   <li>both are complete or both are incomplete</li>
1010         *   <li>both have the same tags</li>
1011         * </ol>
1012         * @param other
1013         * @return true if this primitive and other are equal with respect to their
1014         * semantic attributes.
1015         */
1016        public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1017            if (!isNew() &&  id != other.id)
1018                return false;
1019            //        if (isIncomplete() && ! other.isIncomplete() || !isIncomplete()  && other.isIncomplete())
1020            if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
1021                return false;
1022            // can't do an equals check on the internal keys array because it is not ordered
1023            //
1024            return hasSameTags(other);
1025        }
1026    
1027        /**
1028         * Replies true if this primitive and other are equal with respect to their
1029         * technical attributes. The attributes:
1030         * <ol>
1031         *   <li>deleted</ol>
1032         *   <li>modified</ol>
1033         *   <li>timestamp</ol>
1034         *   <li>version</ol>
1035         *   <li>visible</ol>
1036         *   <li>user</ol>
1037         * </ol>
1038         * have to be equal
1039         * @param other the other primitive
1040         * @return true if this primitive and other are equal with respect to their
1041         * technical attributes
1042         */
1043        public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1044            if (other == null) return false;
1045    
1046            return
1047                    isDeleted() == other.isDeleted()
1048                    && isModified() == other.isModified()
1049                    && timestamp == other.timestamp
1050                    && version == other.version
1051                    && isVisible() == other.isVisible()
1052                    && (user == null ? other.user==null : user==other.user)
1053                    && changesetId == other.changesetId;
1054        }
1055    
1056        /**
1057         * Loads (clone) this primitive from provided PrimitiveData
1058         * @param data
1059         */
1060        public void load(PrimitiveData data) {
1061            // Write lock is provided by subclasses
1062            setKeys(data.getKeys());
1063            setTimestamp(data.getTimestamp());
1064            user = data.getUser();
1065            setChangesetId(data.getChangesetId());
1066            setDeleted(data.isDeleted());
1067            setModified(data.isModified());
1068            setIncomplete(data.isIncomplete());
1069            version = data.getVersion();
1070        }
1071    
1072        /**
1073         * Save parameters of this primitive to the transport object
1074         * @return
1075         */
1076        public abstract PrimitiveData save();
1077    
1078        protected void saveCommonAttributes(PrimitiveData data) {
1079            data.setId(id);
1080            data.setKeys(getKeys());
1081            data.setTimestamp(getTimestamp());
1082            data.setUser(user);
1083            data.setDeleted(isDeleted());
1084            data.setModified(isModified());
1085            data.setVisible(isVisible());
1086            data.setIncomplete(isIncomplete());
1087            data.setChangesetId(changesetId);
1088            data.setVersion(version);
1089        }
1090    
1091        public abstract BBox getBBox();
1092    
1093        /**
1094         * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1095         */
1096        public abstract void updatePosition();
1097    
1098        /*----------------
1099         * OBJECT METHODS
1100         *---------------*/
1101    
1102        @Override
1103        protected String getFlagsAsString() {
1104            StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1105    
1106            if (isDisabled()) {
1107                if (isDisabledAndHidden()) {
1108                    builder.append("h");
1109                } else {
1110                    builder.append("d");
1111                }
1112            }
1113            if (isTagged()) {
1114                builder.append("T");
1115            }
1116            if (hasDirectionKeys()) {
1117                if (reversedDirection()) {
1118                    builder.append("<");
1119                } else {
1120                    builder.append(">");
1121                }
1122            }
1123            return builder.toString();
1124        }
1125    
1126        /**
1127         * Equal, if the id (and class) is equal.
1128         *
1129         * An primitive is equal to its incomplete counter part.
1130         */
1131        @Override public boolean equals(Object obj) {
1132            if (obj instanceof OsmPrimitive)
1133                return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
1134            return false;
1135        }
1136    
1137        /**
1138         * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1139         *
1140         * An primitive has the same hashcode as its incomplete counterpart.
1141         */
1142        @Override public final int hashCode() {
1143            return (int)id;
1144        }
1145    
1146        /**
1147         * Replies the display name of a primitive formatted by <code>formatter</code>
1148         *
1149         * @return the display name
1150         */
1151        public abstract String getDisplayName(NameFormatter formatter);
1152    
1153        @Override
1154        public Collection<String> getTemplateKeys() {
1155            Collection<String> keySet = keySet();
1156            List<String> result = new ArrayList<String>(keySet.size() + 2);
1157            result.add(SPECIAL_VALUE_ID);
1158            result.add(SPECIAL_VALUE_LOCAL_NAME);
1159            result.addAll(keySet);
1160            return result;
1161        }
1162    
1163        @Override
1164        public Object getTemplateValue(String name, boolean special) {
1165            if (special) {
1166                String lc = name.toLowerCase();
1167                if (SPECIAL_VALUE_ID.equals(lc))
1168                    return getId();
1169                else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1170                    return getLocalName();
1171                else
1172                    return null;
1173    
1174            } else
1175                return getIgnoreCase(name);
1176        }
1177    
1178        @Override
1179        public boolean evaluateCondition(Match condition) {
1180            return condition.match(this);
1181        }
1182    
1183        /**
1184         * Replies the set of referring relations
1185         *
1186         * @return the set of referring relations
1187         */
1188        public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1189            HashSet<Relation> ret = new HashSet<Relation>();
1190            for (OsmPrimitive w : primitives) {
1191                ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1192            }
1193            return ret;
1194        }
1195    }