001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.styleelement;
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.Preferences.PreferenceChangeEvent;
011import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.tools.LanguageInfo;
014
015/**
016 * <p>Provides an abstract parent class and three concrete sub classes for various
017 * strategies on how to compose the text label which can be rendered close to a node
018 * or within an area in an OSM map.</p>
019 *
020 * <p>The three strategies below support three rules for composing a label:
021 * <ul>
022 *   <li>{@link StaticLabelCompositionStrategy} - the label is given by a static text
023 *   specified in the MapCSS style file</li>
024 *
025 *   <li>{@link TagLookupCompositionStrategy} - the label is given by the content of a
026 *   tag whose name specified in the MapCSS style file</li>
027 *
028 *   <li>{@link DeriveLabelFromNameTagsCompositionStrategy} - the label is given by the value
029 *   of one
030 *   of the configured "name tags". The list of relevant name tags can be configured
031 *   in the JOSM preferences
032 *   content of a tag whose name specified in the MapCSS style file, see the preference
033 *   options <tt>mappaint.nameOrder</tt> and <tt>mappaint.nameComplementOrder</tt>.</li>
034 * </ul>
035 *
036 */
037public abstract class LabelCompositionStrategy {
038
039    /**
040     * Replies the text value to be rendered as label for the primitive {@code primitive}.
041     *
042     * @param primitive the primitive
043     *
044     * @return the text value to be rendered or null, if primitive is null or
045     * if no suitable value could be composed
046     */
047    public abstract String compose(OsmPrimitive primitive);
048
049    public static class StaticLabelCompositionStrategy extends LabelCompositionStrategy {
050        private final String defaultLabel;
051
052        public StaticLabelCompositionStrategy(String defaultLabel) {
053            this.defaultLabel = defaultLabel;
054        }
055
056        @Override
057        public String compose(OsmPrimitive primitive) {
058            return defaultLabel;
059        }
060
061        public String getDefaultLabel() {
062            return defaultLabel;
063        }
064
065        @Override
066        public String toString() {
067            return '{' + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + '}';
068        }
069
070        @Override
071        public int hashCode() {
072            final int prime = 31;
073            int result = 1;
074            result = prime * result + ((defaultLabel == null) ? 0 : defaultLabel.hashCode());
075            return result;
076        }
077
078        @Override
079        public boolean equals(Object obj) {
080            if (this == obj)
081                return true;
082            if (obj == null)
083                return false;
084            if (getClass() != obj.getClass())
085                return false;
086            StaticLabelCompositionStrategy other = (StaticLabelCompositionStrategy) obj;
087            if (defaultLabel == null) {
088                if (other.defaultLabel != null)
089                    return false;
090            } else if (!defaultLabel.equals(other.defaultLabel))
091                return false;
092            return true;
093        }
094    }
095
096    public static class TagLookupCompositionStrategy extends LabelCompositionStrategy {
097
098        private final String defaultLabelTag;
099
100        public TagLookupCompositionStrategy(String defaultLabelTag) {
101            if (defaultLabelTag != null) {
102                defaultLabelTag = defaultLabelTag.trim();
103                if (defaultLabelTag.isEmpty()) {
104                    defaultLabelTag = null;
105                }
106            }
107            this.defaultLabelTag = defaultLabelTag;
108        }
109
110        @Override
111        public String compose(OsmPrimitive primitive) {
112            if (defaultLabelTag == null) return null;
113            if (primitive == null) return null;
114            return primitive.get(defaultLabelTag);
115        }
116
117        public String getDefaultLabelTag() {
118            return defaultLabelTag;
119        }
120
121        @Override
122        public String toString() {
123            return '{' + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + '}';
124        }
125
126        @Override
127        public int hashCode() {
128            final int prime = 31;
129            int result = 1;
130            result = prime * result + ((defaultLabelTag == null) ? 0 : defaultLabelTag.hashCode());
131            return result;
132        }
133
134        @Override
135        public boolean equals(Object obj) {
136            if (this == obj)
137                return true;
138            if (obj == null)
139                return false;
140            if (getClass() != obj.getClass())
141                return false;
142            TagLookupCompositionStrategy other = (TagLookupCompositionStrategy) obj;
143            if (defaultLabelTag == null) {
144                if (other.defaultLabelTag != null)
145                    return false;
146            } else if (!defaultLabelTag.equals(other.defaultLabelTag))
147                return false;
148            return true;
149        }
150    }
151
152    public static class DeriveLabelFromNameTagsCompositionStrategy
153        extends LabelCompositionStrategy implements PreferenceChangedListener {
154
155        /**
156         * The list of default name tags from which a label candidate is derived.
157         */
158        private static final String[] DEFAULT_NAME_TAGS = {
159            "name:" + LanguageInfo.getJOSMLocaleCode(),
160            "name",
161            "int_name",
162            "distance",
163            "ref",
164            "operator",
165            "brand",
166            "addr:housenumber"
167        };
168
169        /**
170         * The list of default name complement tags from which a label candidate is derived.
171         */
172        private static final String[] DEFAULT_NAME_COMPLEMENT_TAGS = {
173            "capacity"
174        };
175
176        private List<String> nameTags = new ArrayList<>();
177        private List<String> nameComplementTags = new ArrayList<>();
178
179        /**
180         * <p>Creates the strategy and initializes its name tags from the preferences.</p>
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            List<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            StringBuilder name = new StringBuilder();
264            if (!n.hasKeys()) return null;
265            for (String rn : nameTags) {
266                String val = n.get(rn);
267                if (val != null) {
268                    name.append(val);
269                    break;
270                }
271            }
272            for (String rn : nameComplementTags) {
273                String comp = n.get(rn);
274                if (comp != null) {
275                    if (name.length() == 0) {
276                        name.append(comp);
277                    } else {
278                        name.append(" (").append(comp).append(')');
279                    }
280                    break;
281                }
282            }
283            return name.toString();
284        }
285
286        @Override
287        public String compose(OsmPrimitive primitive) {
288            if (primitive == null) return null;
289            return getPrimitiveName(primitive);
290        }
291
292        @Override
293        public String toString() {
294            return "{" + getClass().getSimpleName() +'}';
295        }
296
297        @Override
298        public void preferenceChanged(PreferenceChangeEvent e) {
299            if (e.getKey() != null && e.getKey().startsWith("mappaint.name")) {
300                initNameTagsFromPreferences();
301            }
302        }
303    }
304}