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 *   options <tt>mappaint.nameOrder</tt> and <tt>mappaint.nameComplementOrder</tt>.</li>
032 * </ul>
033 *
034 */
035public abstract class LabelCompositionStrategy {
036
037    /**
038     * Replies the text value to be rendered as label for the primitive {@code primitive}.
039     *
040     * @param primitive the primitive
041     *
042     * @return the text value to be rendered or null, if primitive is null or
043     * if no suitable value could be composed
044     */
045    public abstract String compose(OsmPrimitive primitive);
046
047    public static class StaticLabelCompositionStrategy extends LabelCompositionStrategy {
048        private String defaultLabel;
049
050        public StaticLabelCompositionStrategy(String defaultLabel){
051            this.defaultLabel = defaultLabel;
052        }
053
054        @Override
055        public String compose(OsmPrimitive primitive) {
056            return defaultLabel;
057        }
058
059        public String getDefaultLabel() {
060            return defaultLabel;
061        }
062
063        @Override
064        public String toString() {
065            return "{"  + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + "}";
066        }
067
068        @Override
069        public int hashCode() {
070            final int prime = 31;
071            int result = 1;
072            result = prime * result + ((defaultLabel == null) ? 0 : defaultLabel.hashCode());
073            return result;
074        }
075
076        @Override
077        public boolean equals(Object obj) {
078            if (this == obj)
079                return true;
080            if (obj == null)
081                return false;
082            if (getClass() != obj.getClass())
083                return false;
084            StaticLabelCompositionStrategy other = (StaticLabelCompositionStrategy) obj;
085            if (defaultLabel == null) {
086                if (other.defaultLabel != null)
087                    return false;
088            } else if (!defaultLabel.equals(other.defaultLabel))
089                return false;
090            return true;
091        }
092    }
093
094    public static class TagLookupCompositionStrategy extends LabelCompositionStrategy {
095
096        private String defaultLabelTag;
097        public TagLookupCompositionStrategy(String defaultLabelTag){
098            if (defaultLabelTag != null) {
099                defaultLabelTag = defaultLabelTag.trim();
100                if (defaultLabelTag.isEmpty()) {
101                    defaultLabelTag = null;
102                }
103            }
104            this.defaultLabelTag = defaultLabelTag;
105        }
106
107        @Override
108        public String compose(OsmPrimitive primitive) {
109            if (defaultLabelTag == null) return null;
110            if (primitive == null) return null;
111            return primitive.get(defaultLabelTag);
112        }
113
114        public String getDefaultLabelTag() {
115            return defaultLabelTag;
116        }
117
118        @Override
119        public String toString() {
120            return "{" + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + "}";
121        }
122
123        @Override
124        public int hashCode() {
125            final int prime = 31;
126            int result = 1;
127            result = prime * result + ((defaultLabelTag == null) ? 0 : defaultLabelTag.hashCode());
128            return result;
129        }
130
131        @Override
132        public boolean equals(Object obj) {
133            if (this == obj)
134                return true;
135            if (obj == null)
136                return false;
137            if (getClass() != obj.getClass())
138                return false;
139            TagLookupCompositionStrategy other = (TagLookupCompositionStrategy) obj;
140            if (defaultLabelTag == null) {
141                if (other.defaultLabelTag != null)
142                    return false;
143            } else if (!defaultLabelTag.equals(other.defaultLabelTag))
144                return false;
145            return true;
146        }
147    }
148
149    public static class DeriveLabelFromNameTagsCompositionStrategy extends LabelCompositionStrategy {
150
151        /**
152         * The list of default name tags from which a label candidate is derived.
153         */
154        private static final String[] DEFAULT_NAME_TAGS = {
155            "name:" + LanguageInfo.getJOSMLocaleCode(),
156            "name",
157            "int_name",
158            "ref",
159            "operator",
160            "brand",
161            "addr:housenumber"
162        };
163
164        /**
165         * The list of default name complement tags from which a label candidate is derived.
166         */
167        private static final String[] DEFAULT_NAME_COMPLEMENT_TAGS = {
168            "capacity"
169        };
170
171        private List<String> nameTags = new ArrayList<>();
172        private List<String> nameComplementTags = new ArrayList<>();
173
174        /**
175         * <p>Creates the strategy and initializes its name tags from the preferences.</p>
176         *
177         * <p><strong>Note:</strong> If the list of name tags in the preferences changes, strategy instances
178         * are not notified. It's up to the client to listen to preference changes and
179         * invoke {@link #initNameTagsFromPreferences()} accordingly.</p>
180         *
181         */
182        public DeriveLabelFromNameTagsCompositionStrategy() {
183            initNameTagsFromPreferences();
184        }
185
186        private static List<String> buildNameTags(List<String> nameTags) {
187            if (nameTags == null) {
188                nameTags = Collections.emptyList();
189            }
190            ArrayList<String> result = new ArrayList<>();
191            for(String tag: nameTags) {
192                if (tag == null) {
193                    continue;
194                }
195                tag = tag.trim();
196                if (tag.isEmpty()) {
197                    continue;
198                }
199                result.add(tag);
200            }
201            return result;
202        }
203
204        /**
205         * Sets the name tags to be looked up in order to build up the label.
206         *
207         * @param nameTags the name tags. null values are ignored.
208         */
209        public void setNameTags(List<String> nameTags){
210            this.nameTags = buildNameTags(nameTags);
211        }
212
213        /**
214         * Sets the name complement tags to be looked up in order to build up the label.
215         *
216         * @param nameComplementTags the name complement tags. null values are ignored.
217         * @since 6541
218         */
219        public void setNameComplementTags(List<String> nameComplementTags){
220            this.nameComplementTags = buildNameTags(nameComplementTags);
221        }
222
223        /**
224         * Replies an unmodifiable list of the name tags used to compose the label.
225         *
226         * @return the list of name tags
227         */
228        public List<String> getNameTags() {
229            return Collections.unmodifiableList(nameTags);
230        }
231
232        /**
233         * Replies an unmodifiable list of the name complement tags used to compose the label.
234         *
235         * @return the list of name complement tags
236         * @since 6541
237         */
238        public List<String> getNameComplementTags() {
239            return Collections.unmodifiableList(nameComplementTags);
240        }
241
242        /**
243         * Initializes the name tags to use from a list of default name tags (see
244         * {@link #DEFAULT_NAME_TAGS} and {@link #DEFAULT_NAME_COMPLEMENT_TAGS})
245         * and from name tags configured in the preferences using the keys
246         * <tt>mappaint.nameOrder</tt> and <tt>mappaint.nameComplementOrder</tt>.
247         */
248        public final void initNameTagsFromPreferences() {
249            if (Main.pref == null){
250                this.nameTags = new ArrayList<>(Arrays.asList(DEFAULT_NAME_TAGS));
251                this.nameComplementTags = new ArrayList<>(Arrays.asList(DEFAULT_NAME_COMPLEMENT_TAGS));
252            } else {
253                this.nameTags = new ArrayList<>(
254                        Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(DEFAULT_NAME_TAGS))
255                );
256                this.nameComplementTags = new ArrayList<>(
257                        Main.pref.getCollection("mappaint.nameComplementOrder", Arrays.asList(DEFAULT_NAME_COMPLEMENT_TAGS))
258                );
259            }
260        }
261
262        private String getPrimitiveName(OsmPrimitive n) {
263            String name = null;
264            if (!n.hasKeys()) return null;
265            for (String rn : nameTags) {
266                name = n.get(rn);
267                if (name != null) {
268                    break;
269                }
270            }
271            for (String rn : nameComplementTags) {
272                String comp = n.get(rn);
273                if (comp != null) {
274                    if (name == null) {
275                        name = comp;
276                    } else {
277                        name += " (" + comp + ")";
278                    }
279                    break;
280                }
281            }
282            return name;
283        }
284
285        @Override
286        public String compose(OsmPrimitive primitive) {
287            if (primitive == null) return null;
288            return getPrimitiveName(primitive);
289        }
290
291        @Override
292        public String toString() {
293            return "{" + getClass().getSimpleName() +"}";
294        }
295    }
296}