source: josm/trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java@ 8260

Last change on this file since 8260 was 8126, checked in by Don-vip, 9 years ago

fix Sonar issue squid:S2444 - Lazy initialization of "static" fields should be "synchronized"

  • Property svn:eol-style set to native
File size: 24.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6import static org.openstreetmap.josm.tools.I18n.trc_lazy;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.ComponentOrientation;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Comparator;
15import java.util.HashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Locale;
19import java.util.Map;
20import java.util.Set;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.coor.CoordinateFormat;
24import org.openstreetmap.josm.data.coor.LatLon;
25import org.openstreetmap.josm.data.osm.Changeset;
26import org.openstreetmap.josm.data.osm.IPrimitive;
27import org.openstreetmap.josm.data.osm.IRelation;
28import org.openstreetmap.josm.data.osm.NameFormatter;
29import org.openstreetmap.josm.data.osm.Node;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.data.osm.OsmUtils;
32import org.openstreetmap.josm.data.osm.Relation;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.data.osm.history.HistoryNameFormatter;
35import org.openstreetmap.josm.data.osm.history.HistoryNode;
36import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
37import org.openstreetmap.josm.data.osm.history.HistoryRelation;
38import org.openstreetmap.josm.data.osm.history.HistoryWay;
39import org.openstreetmap.josm.gui.tagging.TaggingPreset;
40import org.openstreetmap.josm.gui.tagging.TaggingPresetNameTemplateList;
41import org.openstreetmap.josm.tools.AlphanumComparator;
42import org.openstreetmap.josm.tools.I18n;
43import org.openstreetmap.josm.tools.Utils;
44import org.openstreetmap.josm.tools.Utils.Function;
45
46/**
47 * This is the default implementation of a {@link NameFormatter} for names of {@link OsmPrimitive}s.
48 *
49 */
50public class DefaultNameFormatter implements NameFormatter, HistoryNameFormatter {
51
52 private static DefaultNameFormatter instance;
53
54 private static final List<NameFormatterHook> formatHooks = new LinkedList<>();
55
56 /**
57 * Replies the unique instance of this formatter
58 *
59 * @return the unique instance of this formatter
60 */
61 public static synchronized DefaultNameFormatter getInstance() {
62 if (instance == null) {
63 instance = new DefaultNameFormatter();
64 }
65 return instance;
66 }
67
68 /**
69 * Registers a format hook. Adds the hook at the first position of the format hooks.
70 * (for plugins)
71 *
72 * @param hook the format hook. Ignored if null.
73 */
74 public static void registerFormatHook(NameFormatterHook hook) {
75 if (hook == null) return;
76 if (!formatHooks.contains(hook)) {
77 formatHooks.add(0,hook);
78 }
79 }
80
81 /**
82 * Unregisters a format hook. Removes the hook from the list of format hooks.
83 *
84 * @param hook the format hook. Ignored if null.
85 */
86 public static void unregisterFormatHook(NameFormatterHook hook) {
87 if (hook == null) return;
88 if (formatHooks.contains(hook)) {
89 formatHooks.remove(hook);
90 }
91 }
92
93 /** The default list of tags which are used as naming tags in relations.
94 * A ? prefix indicates a boolean value, for which the key (instead of the value) is used.
95 */
96 public static final String[] DEFAULT_NAMING_TAGS_FOR_RELATIONS = {"name", "ref", "restriction", "landuse", "natural",
97 "public_transport", ":LocationCode", "note", "?building"};
98
99 /** the current list of tags used as naming tags in relations */
100 private static List<String> namingTagsForRelations = null;
101
102 /**
103 * Replies the list of naming tags used in relations. The list is given (in this order) by:
104 * <ul>
105 * <li>by the tag names in the preference <tt>relation.nameOrder</tt></li>
106 * <li>by the default tags in {@link #DEFAULT_NAMING_TAGS_FOR_RELATIONS}
107 * </ul>
108 *
109 * @return the list of naming tags used in relations
110 */
111 public static synchronized List<String> getNamingtagsForRelations() {
112 if (namingTagsForRelations == null) {
113 namingTagsForRelations = new ArrayList<>(
114 Main.pref.getCollection("relation.nameOrder", Arrays.asList(DEFAULT_NAMING_TAGS_FOR_RELATIONS))
115 );
116 }
117 return namingTagsForRelations;
118 }
119
120 /**
121 * Decorates the name of primitive with its id, if the preference
122 * <tt>osm-primitives.showid</tt> is set. Shows unique id if osm-primitives.showid.new-primitives is set
123 *
124 * @param name the name without the id
125 * @param primitive the primitive
126 */
127 protected void decorateNameWithId(StringBuilder name, IPrimitive primitive) {
128 if (Main.pref.getBoolean("osm-primitives.showid")) {
129 if (Main.pref.getBoolean("osm-primitives.showid.new-primitives")) {
130 name.append(tr(" [id: {0}]", primitive.getUniqueId()));
131 } else {
132 name.append(tr(" [id: {0}]", primitive.getId()));
133 }
134 }
135 }
136
137 /**
138 * Formats a name for a node
139 *
140 * @param node the node
141 * @return the name
142 */
143 @Override
144 public String format(Node node) {
145 StringBuilder name = new StringBuilder();
146 if (node.isIncomplete()) {
147 name.append(tr("incomplete"));
148 } else {
149 TaggingPreset preset = TaggingPresetNameTemplateList.getInstance().findPresetTemplate(node);
150 if (preset == null) {
151 String n;
152 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
153 n = node.getLocalName();
154 } else {
155 n = node.getName();
156 }
157 if(n == null)
158 {
159 String s;
160 if((s = node.get("addr:housename")) != null) {
161 /* I18n: name of house as parameter */
162 n = tr("House {0}", s);
163 }
164 if(n == null && (s = node.get("addr:housenumber")) != null) {
165 String t = node.get("addr:street");
166 if(t != null) {
167 /* I18n: house number, street as parameter, number should remain
168 before street for better visibility */
169 n = tr("House number {0} at {1}", s, t);
170 }
171 else {
172 /* I18n: house number as parameter */
173 n = tr("House number {0}", s);
174 }
175 }
176 }
177
178 if (n == null) {
179 n = node.isNew() ? tr("node") : Long.toString(node.getId());
180 }
181 name.append(n);
182 } else {
183 preset.nameTemplate.appendText(name, node);
184 }
185 if (node.getCoor() != null) {
186 name.append(" \u200E(").append(node.getCoor().latToString(CoordinateFormat.getDefaultFormat())).append(", ").append(node.getCoor().lonToString(CoordinateFormat.getDefaultFormat())).append(")");
187 }
188 }
189 decorateNameWithId(name, node);
190
191
192 String result = name.toString();
193 for (NameFormatterHook hook: formatHooks) {
194 String hookResult = hook.checkFormat(node, result);
195 if (hookResult != null)
196 return hookResult;
197 }
198
199 return result;
200 }
201
202 private final Comparator<Node> nodeComparator = new Comparator<Node>() {
203 @Override
204 public int compare(Node n1, Node n2) {
205 return format(n1).compareTo(format(n2));
206 }
207 };
208
209 @Override
210 public Comparator<Node> getNodeComparator() {
211 return nodeComparator;
212 }
213
214
215 /**
216 * Formats a name for a way
217 *
218 * @param way the way
219 * @return the name
220 */
221 @Override
222 public String format(Way way) {
223 StringBuilder name = new StringBuilder();
224
225 char mark = 0;
226 // If current language is left-to-right (almost all languages)
227 if (ComponentOrientation.getOrientation(Locale.getDefault()).isLeftToRight()) {
228 // will insert Left-To-Right Mark to ensure proper display of text in the case when object name is right-to-left
229 mark = '\u200E';
230 } else {
231 // otherwise will insert Right-To-Left Mark to ensure proper display in the opposite case
232 mark = '\u200F';
233 }
234 // Initialize base direction of the string
235 name.append(mark);
236
237 if (way.isIncomplete()) {
238 name.append(tr("incomplete"));
239 } else {
240 TaggingPreset preset = TaggingPresetNameTemplateList.getInstance().findPresetTemplate(way);
241 if (preset == null) {
242 String n;
243 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
244 n = way.getLocalName();
245 } else {
246 n = way.getName();
247 }
248 if (n == null) {
249 n = way.get("ref");
250 }
251 if (n == null) {
252 n = (way.get("highway") != null) ? tr("highway") :
253 (way.get("railway") != null) ? tr("railway") :
254 (way.get("waterway") != null) ? tr("waterway") :
255 (way.get("landuse") != null) ? tr("landuse") : null;
256 }
257 if (n == null) {
258 String s;
259 if((s = way.get("addr:housename")) != null) {
260 /* I18n: name of house as parameter */
261 n = tr("House {0}", s);
262 }
263 if(n == null && (s = way.get("addr:housenumber")) != null) {
264 String t = way.get("addr:street");
265 if(t != null) {
266 /* I18n: house number, street as parameter, number should remain
267 before street for better visibility */
268 n = tr("House number {0} at {1}", s, t);
269 }
270 else {
271 /* I18n: house number as parameter */
272 n = tr("House number {0}", s);
273 }
274 }
275 }
276 if(n == null && way.get("building") != null) n = tr("building");
277 if(n == null || n.length() == 0) {
278 n = String.valueOf(way.getId());
279 }
280
281 name.append(n);
282 } else {
283 preset.nameTemplate.appendText(name, way);
284 }
285
286 int nodesNo = way.getRealNodesCount();
287 /* note: length == 0 should no longer happen, but leave the bracket code
288 nevertheless, who knows what future brings */
289 /* I18n: count of nodes as parameter */
290 String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
291 name.append(mark).append(" (").append(nodes).append(")");
292 }
293 decorateNameWithId(name, way);
294
295 String result = name.toString();
296 for (NameFormatterHook hook: formatHooks) {
297 String hookResult = hook.checkFormat(way, result);
298 if (hookResult != null)
299 return hookResult;
300 }
301
302 return result;
303 }
304
305 private final Comparator<Way> wayComparator = new Comparator<Way>() {
306 @Override
307 public int compare(Way w1, Way w2) {
308 return format(w1).compareTo(format(w2));
309 }
310 };
311
312 @Override
313 public Comparator<Way> getWayComparator() {
314 return wayComparator;
315 }
316
317
318 /**
319 * Formats a name for a relation
320 *
321 * @param relation the relation
322 * @return the name
323 */
324 @Override
325 public String format(Relation relation) {
326 StringBuilder name = new StringBuilder();
327 if (relation.isIncomplete()) {
328 name.append(tr("incomplete"));
329 } else {
330 TaggingPreset preset = TaggingPresetNameTemplateList.getInstance().findPresetTemplate(relation);
331
332 formatRelationNameAndType(relation, name, preset);
333
334 int mbno = relation.getMembersCount();
335 name.append(trn("{0} member", "{0} members", mbno, mbno));
336
337 if (relation.hasIncompleteMembers()) {
338 name.append(", ").append(tr("incomplete"));
339 }
340
341 name.append(")");
342 }
343 decorateNameWithId(name, relation);
344
345 String result = name.toString();
346 for (NameFormatterHook hook: formatHooks) {
347 String hookResult = hook.checkFormat(relation, result);
348 if (hookResult != null)
349 return hookResult;
350 }
351
352 return result;
353 }
354
355 private void formatRelationNameAndType(Relation relation, StringBuilder result, TaggingPreset preset) {
356 if (preset == null) {
357 result.append(getRelationTypeName(relation));
358 String relationName = getRelationName(relation);
359 if (relationName == null) {
360 relationName = Long.toString(relation.getId());
361 } else {
362 relationName = "\"" + relationName + "\"";
363 }
364 result.append(" (").append(relationName).append(", ");
365 } else {
366 preset.nameTemplate.appendText(result, relation);
367 result.append("(");
368 }
369 }
370
371 private final Comparator<Relation> relationComparator = new Comparator<Relation>() {
372 @Override
373 public int compare(Relation r1, Relation r2) {
374 //TODO This doesn't work correctly with formatHooks
375
376 TaggingPreset preset1 = TaggingPresetNameTemplateList.getInstance().findPresetTemplate(r1);
377 TaggingPreset preset2 = TaggingPresetNameTemplateList.getInstance().findPresetTemplate(r2);
378
379 if (preset1 != null || preset2 != null) {
380 StringBuilder name1 = new StringBuilder();
381 formatRelationNameAndType(r1, name1, preset1);
382 StringBuilder name2 = new StringBuilder();
383 formatRelationNameAndType(r2, name2, preset2);
384
385 int comp = AlphanumComparator.getInstance().compare(name1.toString(), name2.toString());
386 if (comp != 0)
387 return comp;
388 } else {
389
390 String type1 = getRelationTypeName(r1);
391 String type2 = getRelationTypeName(r2);
392
393 int comp = AlphanumComparator.getInstance().compare(type1, type2);
394 if (comp != 0)
395 return comp;
396
397 String name1 = getRelationName(r1);
398 String name2 = getRelationName(r2);
399
400 comp = AlphanumComparator.getInstance().compare(name1, name2);
401 if (comp != 0)
402 return comp;
403 }
404
405 if (r1.getMembersCount() != r2.getMembersCount())
406 return (r1.getMembersCount() > r2.getMembersCount())?1:-1;
407
408 int comp = Boolean.valueOf(r1.hasIncompleteMembers()).compareTo(Boolean.valueOf(r2.hasIncompleteMembers()));
409 if (comp != 0)
410 return comp;
411
412 if (r1.getUniqueId() > r2.getUniqueId())
413 return 1;
414 else if (r1.getUniqueId() < r2.getUniqueId())
415 return -1;
416 else
417 return 0;
418 }
419 };
420
421 @Override
422 public Comparator<Relation> getRelationComparator() {
423 return relationComparator;
424 }
425
426 private String getRelationTypeName(IRelation relation) {
427 String name = trc("Relation type", relation.get("type"));
428 if (name == null) {
429 name = (relation.get("public_transport") != null) ? tr("public transport") : null;
430 }
431 if (name == null) {
432 String building = relation.get("building");
433 if (OsmUtils.isTrue(building)) {
434 name = tr("building");
435 } else if(building != null)
436 {
437 name = tr(building); // translate tag!
438 }
439 }
440 if (name == null) {
441 name = trc("Place type", relation.get("place"));
442 }
443 if (name == null) {
444 name = tr("relation");
445 }
446 String admin_level = relation.get("admin_level");
447 if (admin_level != null) {
448 name += "["+admin_level+"]";
449 }
450
451 for (NameFormatterHook hook: formatHooks) {
452 String hookResult = hook.checkRelationTypeName(relation, name);
453 if (hookResult != null)
454 return hookResult;
455 }
456
457 return name;
458 }
459
460 private String getNameTagValue(IRelation relation, String nameTag) {
461 if ("name".equals(nameTag)) {
462 if (Main.pref.getBoolean("osm-primitives.localize-name", true))
463 return relation.getLocalName();
464 else
465 return relation.getName();
466 } else if (":LocationCode".equals(nameTag)) {
467 for (String m : relation.keySet()) {
468 if (m.endsWith(nameTag))
469 return relation.get(m);
470 }
471 return null;
472 } else if (nameTag.startsWith("?") && OsmUtils.isTrue(relation.get(nameTag.substring(1)))) {
473 return tr(nameTag.substring(1));
474 } else if (nameTag.startsWith("?") && OsmUtils.isFalse(relation.get(nameTag.substring(1)))) {
475 return null;
476 } else if (nameTag.startsWith("?")) {
477 return trc_lazy(nameTag, I18n.escape(relation.get(nameTag.substring(1))));
478 } else {
479 return trc_lazy(nameTag, I18n.escape(relation.get(nameTag)));
480 }
481 }
482
483 private String getRelationName(IRelation relation) {
484 String nameTag = null;
485 for (String n : getNamingtagsForRelations()) {
486 nameTag = getNameTagValue(relation, n);
487 if (nameTag != null)
488 return nameTag;
489 }
490 return null;
491 }
492
493 /**
494 * Formats a name for a changeset
495 *
496 * @param changeset the changeset
497 * @return the name
498 */
499 @Override
500 public String format(Changeset changeset) {
501 return tr("Changeset {0}",changeset.getId());
502 }
503
504 /**
505 * Builds a default tooltip text for the primitive <code>primitive</code>.
506 *
507 * @param primitive the primitmive
508 * @return the tooltip text
509 */
510 public String buildDefaultToolTip(IPrimitive primitive) {
511 return buildDefaultToolTip(primitive.getId(), primitive.getKeys());
512 }
513
514 private String buildDefaultToolTip(long id, Map<String, String> tags) {
515 StringBuilder sb = new StringBuilder();
516 sb.append("<html>");
517 sb.append("<strong>id</strong>=")
518 .append(id)
519 .append("<br>");
520 List<String> keyList = new ArrayList<>(tags.keySet());
521 Collections.sort(keyList);
522 for (int i = 0; i < keyList.size(); i++) {
523 if (i > 0) {
524 sb.append("<br>");
525 }
526 String key = keyList.get(i);
527 sb.append("<strong>")
528 .append(key)
529 .append("</strong>")
530 .append("=");
531 String value = tags.get(key);
532 while(value.length() != 0) {
533 sb.append(value.substring(0,Math.min(50, value.length())));
534 if (value.length() > 50) {
535 sb.append("<br>");
536 value = value.substring(50);
537 } else {
538 value = "";
539 }
540 }
541 }
542 sb.append("</html>");
543 return sb.toString();
544 }
545
546 /**
547 * Decorates the name of primitive with its id, if the preference
548 * <tt>osm-primitives.showid</tt> is set.
549 *
550 * The id is append to the {@link StringBuilder} passed in <code>name</code>.
551 *
552 * @param name the name without the id
553 * @param primitive the primitive
554 */
555 protected void decorateNameWithId(StringBuilder name, HistoryOsmPrimitive primitive) {
556 if (Main.pref.getBoolean("osm-primitives.showid")) {
557 name.append(tr(" [id: {0}]", primitive.getId()));
558 }
559 }
560
561 /**
562 * Formats a name for a history node
563 *
564 * @param node the node
565 * @return the name
566 */
567 @Override
568 public String format(HistoryNode node) {
569 StringBuilder sb = new StringBuilder();
570 String name;
571 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
572 name = node.getLocalName();
573 } else {
574 name = node.getName();
575 }
576 if (name == null) {
577 sb.append(node.getId());
578 } else {
579 sb.append(name);
580 }
581 LatLon coord = node.getCoords();
582 if (coord != null) {
583 sb.append(" (")
584 .append(coord.latToString(CoordinateFormat.getDefaultFormat()))
585 .append(", ")
586 .append(coord.lonToString(CoordinateFormat.getDefaultFormat()))
587 .append(")");
588 }
589 decorateNameWithId(sb, node);
590 return sb.toString();
591 }
592
593 /**
594 * Formats a name for a way
595 *
596 * @param way the way
597 * @return the name
598 */
599 @Override
600 public String format(HistoryWay way) {
601 StringBuilder sb = new StringBuilder();
602 String name;
603 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
604 name = way.getLocalName();
605 } else {
606 name = way.getName();
607 }
608 if (name != null) {
609 sb.append(name);
610 }
611 if (sb.length() == 0 && way.get("ref") != null) {
612 sb.append(way.get("ref"));
613 }
614 if (sb.length() == 0) {
615 sb.append(
616 (way.get("highway") != null) ? tr("highway") :
617 (way.get("railway") != null) ? tr("railway") :
618 (way.get("waterway") != null) ? tr("waterway") :
619 (way.get("landuse") != null) ? tr("landuse") : ""
620 );
621 }
622
623 int nodesNo = way.isClosed() ? way.getNumNodes() -1 : way.getNumNodes();
624 String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
625 if(sb.length() == 0 ) {
626 sb.append(way.getId());
627 }
628 /* note: length == 0 should no longer happen, but leave the bracket code
629 nevertheless, who knows what future brings */
630 sb.append((sb.length() > 0) ? " ("+nodes+")" : nodes);
631 decorateNameWithId(sb, way);
632 return sb.toString();
633 }
634
635 /**
636 * Formats a name for a {@link HistoryRelation})
637 *
638 * @param relation the relation
639 * @return the name
640 */
641 @Override
642 public String format(HistoryRelation relation) {
643 StringBuilder sb = new StringBuilder();
644 if (relation.get("type") != null) {
645 sb.append(relation.get("type"));
646 } else {
647 sb.append(tr("relation"));
648 }
649 sb.append(" (");
650 String nameTag = null;
651 Set<String> namingTags = new HashSet<>(getNamingtagsForRelations());
652 for (String n : relation.getTags().keySet()) {
653 // #3328: "note " and " note" are name tags too
654 if (namingTags.contains(n.trim())) {
655 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
656 nameTag = relation.getLocalName();
657 } else {
658 nameTag = relation.getName();
659 }
660 if (nameTag == null) {
661 nameTag = relation.get(n);
662 }
663 }
664 if (nameTag != null) {
665 break;
666 }
667 }
668 if (nameTag == null) {
669 sb.append(Long.toString(relation.getId())).append(", ");
670 } else {
671 sb.append("\"").append(nameTag).append("\", ");
672 }
673
674 int mbno = relation.getNumMembers();
675 sb.append(trn("{0} member", "{0} members", mbno, mbno)).append(")");
676
677 decorateNameWithId(sb, relation);
678 return sb.toString();
679 }
680
681 /**
682 * Builds a default tooltip text for an HistoryOsmPrimitive <code>primitive</code>.
683 *
684 * @param primitive the primitmive
685 * @return the tooltip text
686 */
687 public String buildDefaultToolTip(HistoryOsmPrimitive primitive) {
688 return buildDefaultToolTip(primitive.getId(), primitive.getTags());
689 }
690
691 public String formatAsHtmlUnorderedList(Collection<? extends OsmPrimitive> primitives) {
692 return Utils.joinAsHtmlUnorderedList(Utils.transform(primitives, new Function<OsmPrimitive, String>() {
693
694 @Override
695 public String apply(OsmPrimitive x) {
696 return x.getDisplayName(DefaultNameFormatter.this);
697 }
698 }));
699 }
700
701 public String formatAsHtmlUnorderedList(OsmPrimitive... primitives) {
702 return formatAsHtmlUnorderedList(Arrays.asList(primitives));
703 }
704}
Note: See TracBrowser for help on using the repository browser.