source: josm/trunk/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java

Last change on this file was 18811, checked in by taylor.smock, 8 months ago

Fix #23124: Azul Java 17.0.4.1 will cause a NumberFormatException

Java can have multiple dots instead of just two. Specifically, the Java version
specifier is $MAJOR.$MINOR.$SECURITY.$PATCH.

Previously, we assumed that there would only ever be two dots in the version
specifier, and got the first and last dot. Now we get the first and second dot.

In addition, this fixes some lint issues.

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