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

Last change on this file since 5531 was 5531, checked in by stoecker, 10 years ago

fix #8109 - drop some yh: tags automatically

  • Property svn:eol-style set to native
File size: 40.6 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Date;
12import java.util.HashSet;
13import java.util.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.actions.search.SearchCompiler;
21import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
22import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
23import org.openstreetmap.josm.data.osm.visitor.Visitor;
24import org.openstreetmap.josm.gui.mappaint.StyleCache;
25import org.openstreetmap.josm.tools.CheckParameterUtil;
26import org.openstreetmap.josm.tools.Predicate;
27import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
28
29/**
30 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
31 * and updated within the OSM-Server.
32 *
33 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
34 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
35 * by the server environment and not an extendible data stuff.
36 *
37 * @author imi
38 */
39abstract public class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, TemplateEngineDataProvider {
40    private static final String SPECIAL_VALUE_ID = "id";
41    private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
42
43
44    /**
45     * An object can be disabled by the filter mechanism.
46     * Then it will show in a shade of gray on the map or it is completely
47     * hidden from the view.
48     * Disabled objects usually cannot be selected or modified
49     * while the filter is active.
50     */
51    protected static final int FLAG_DISABLED = 1 << 4;
52
53    /**
54     * This flag is only relevant if an object is disabled by the
55     * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
56     * Then it indicates, whether it is completely hidden or
57     * just shown in gray color.
58     *
59     * When the primitive is not disabled, this flag should be
60     * unset as well (for efficient access).
61     */
62    protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5;
63
64    /**
65     * Flag used internally by the filter mechanism.
66     */
67    protected static final int FLAG_DISABLED_TYPE = 1 << 6;
68
69    /**
70     * Flag used internally by the filter mechanism.
71     */
72    protected static final int FLAG_HIDDEN_TYPE = 1 << 7;
73
74    /**
75     * This flag is set if the primitive is a way and
76     * according to the tags, the direction of the way is important.
77     * (e.g. one way street.)
78     */
79    protected static final int FLAG_HAS_DIRECTIONS = 1 << 8;
80
81    /**
82     * If the primitive is tagged.
83     * Some trivial tags like source=* are ignored here.
84     */
85    protected static final int FLAG_TAGGED = 1 << 9;
86
87    /**
88     * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
89     * It shows, that direction of the arrows should be reversed.
90     * (E.g. oneway=-1.)
91     */
92    protected static final int FLAG_DIRECTION_REVERSED = 1 << 10;
93
94    /**
95     * When hovering over ways and nodes in add mode, the
96     * "target" objects are visually highlighted. This flag indicates
97     * that the primitive is currently highlighted.
98     */
99    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}
Note: See TracBrowser for help on using the repository browser.