001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Collections;
007import java.util.List;
008
009import org.openstreetmap.josm.Main;
010import org.openstreetmap.josm.data.osm.OsmPrimitive;
011import org.openstreetmap.josm.tools.LanguageInfo;
012
013/**
014 * <p>Provides an abstract parent class and three concrete sub classes for various
015 * strategies on how to compose the text label which can be rendered close to a node
016 * or within an area in an OSM map.</p>
017 *
018 * <p>The three strategies below support three rules for composing a label:
019 * <ul>
020 *   <li>{@link StaticLabelCompositionStrategy} - the label is given by a static text
021 *   specified in the MapCSS style file</li>
022 *
023 *   <li>{@link TagLookupCompositionStrategy} - the label is given by the content of a
024 *   tag whose name specified in the MapCSS style file</li>
025 *
026 *   <li>{@link DeriveLabelFromNameTagsCompositionStrategy} - the label is given by the value
027 *   of one
028 *   of the configured "name tags". The list of relevant name tags can be configured
029 *   in the JOSM preferences
030 *   content of a tag whose name specified in the MapCSS style file, see the preference
031 *   option <tt>mappaint.nameOrder</tt>.</li>
032 * </ul>
033 * </p>
034 *
035 */
036public abstract class LabelCompositionStrategy {
037
038    /**
039     * Replies the text value to be rendered as label for the primitive {@code primitive}.
040     *
041     * @param primitive the primitive
042     *
043     * @return the text value to be rendered or null, if primitive is null or
044     * if no suitable value could be composed
045     */
046    abstract public String compose(OsmPrimitive primitive);
047
048    static public class StaticLabelCompositionStrategy extends LabelCompositionStrategy {
049        private String defaultLabel;
050
051        public StaticLabelCompositionStrategy(String defaultLabel){
052            this.defaultLabel = defaultLabel;
053        }
054
055        @Override
056        public String compose(OsmPrimitive primitive) {
057            return defaultLabel;
058        }
059
060        public String getDefaultLabel() {
061            return defaultLabel;
062        }
063
064        @Override
065        public String toString() {
066            return "{"  + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + "}";
067        }
068
069        @Override
070        public int hashCode() {
071            final int prime = 31;
072            int result = 1;
073            result = prime * result + ((defaultLabel == null) ? 0 : defaultLabel.hashCode());
074            return result;
075        }
076
077        @Override
078        public boolean equals(Object obj) {
079            if (this == obj)
080                return true;
081            if (obj == null)
082                return false;
083            if (getClass() != obj.getClass())
084                return false;
085            StaticLabelCompositionStrategy other = (StaticLabelCompositionStrategy) obj;
086            if (defaultLabel == null) {
087                if (other.defaultLabel != null)
088                    return false;
089            } else if (!defaultLabel.equals(other.defaultLabel))
090                return false;
091            return true;
092        }
093    }
094
095    static public class TagLookupCompositionStrategy extends LabelCompositionStrategy {
096
097        private String defaultLabelTag;
098        public TagLookupCompositionStrategy(String defaultLabelTag){
099            if (defaultLabelTag != null) {
100                defaultLabelTag = defaultLabelTag.trim();
101                if (defaultLabelTag.isEmpty()) {
102                    defaultLabelTag = null;
103                }
104            }
105            this.defaultLabelTag = defaultLabelTag;
106        }
107
108        @Override
109        public String compose(OsmPrimitive primitive) {
110            if (defaultLabelTag == null) return null;
111            if (primitive == null) return null;
112            return primitive.get(defaultLabelTag);
113        }
114
115        public String getDefaultLabelTag() {
116            return defaultLabelTag;
117        }
118
119        @Override
120        public String toString() {
121            return "{" + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + "}";
122        }
123
124        @Override
125        public int hashCode() {
126            final int prime = 31;
127            int result = 1;
128            result = prime * result + ((defaultLabelTag == null) ? 0 : defaultLabelTag.hashCode());
129            return result;
130        }
131
132        @Override
133        public boolean equals(Object obj) {
134            if (this == obj)
135                return true;
136            if (obj == null)
137                return false;
138            if (getClass() != obj.getClass())
139                return false;
140            TagLookupCompositionStrategy other = (TagLookupCompositionStrategy) obj;
141            if (defaultLabelTag == null) {
142                if (other.defaultLabelTag != null)
143                    return false;
144            } else if (!defaultLabelTag.equals(other.defaultLabelTag))
145                return false;
146            return true;
147        }
148    }
149
150    static public class DeriveLabelFromNameTagsCompositionStrategy extends LabelCompositionStrategy {
151
152        /**
153         * The list of default name tags from which a label candidate is derived.
154         */
155        static public final String[] DEFAULT_NAME_TAGS = {
156            "name:" + LanguageInfo.getJOSMLocaleCode(),
157            "name",
158            "int_name",
159            "ref",
160            "operator",
161            "brand",
162            "addr:housenumber"
163        };
164
165        private  List<String> nameTags = new ArrayList<String>();
166
167        /**
168         * <p>Creates the strategy and initializes its name tags from the preferences.</p>
169         *
170         * <p><strong>Note:</strong> If the list of name tags in the preferences changes, strategy instances
171         * are not notified. It's up to the client to listen to preference changes and
172         * invoke {@link #initNameTagsFromPreferences()} accordingly.</p>
173         *
174         */
175        public DeriveLabelFromNameTagsCompositionStrategy() {
176            initNameTagsFromPreferences();
177        }
178
179        /**
180         * Sets the name tags to be looked up in order to build up the label
181         *
182         * @param nameTags the name tags. null values are ignore.
183         */
184        public void setNameTags(List<String> nameTags){
185            if (nameTags == null) {
186                nameTags = Collections.emptyList();
187            }
188            this.nameTags = new ArrayList<String>();
189            for(String tag: nameTags) {
190                if (tag == null) {
191                    continue;
192                }
193                tag = tag.trim();
194                if (tag.isEmpty()) {
195                    continue;
196                }
197                this.nameTags.add(tag);
198            }
199        }
200
201        /**
202         * Replies an unmodifiable list of the name tags used to compose the label.
203         *
204         * @return the list of name tags
205         */
206        public List<String> getNameTags() {
207            return Collections.unmodifiableList(nameTags);
208        }
209
210        /**
211         * Initializes the name tags to use from a list of default name tags (see
212         * {@link #DEFAULT_NAME_TAGS}) and from name tags configured in the preferences
213         * using the preference key <tt>mappaint.nameOrder</tt>.
214         */
215        public void initNameTagsFromPreferences() {
216            if (Main.pref == null){
217                this.nameTags = new ArrayList<String>(Arrays.asList(DEFAULT_NAME_TAGS));
218            } else {
219                this.nameTags = new ArrayList<String>(
220                        Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(DEFAULT_NAME_TAGS))
221                );
222            }
223        }
224
225        private String getPrimitiveName(OsmPrimitive n) {
226            String name = null;
227            if (!n.hasKeys()) return null;
228            for (String rn : nameTags) {
229                name = n.get(rn);
230                if (name != null) return name;
231            }
232            return null;
233        }
234
235        @Override
236        public String compose(OsmPrimitive primitive) {
237            if (primitive == null) return null;
238            return getPrimitiveName(primitive);
239        }
240
241        @Override
242        public String toString() {
243            return "{" + getClass().getSimpleName() +"}";
244        }
245    }
246}