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

Revision 5188, 38.4 KB checked in by simon04, 5 weeks ago (diff)

fix #5983 - do not render incline like oneway

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