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