001// License: GPL. See LICENSE file for details. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.Collection; 007import java.util.HashSet; 008import java.util.Map.Entry; 009 010import org.openstreetmap.josm.data.osm.OsmPrimitive; 011import org.openstreetmap.josm.data.validation.Severity; 012import org.openstreetmap.josm.data.validation.Test; 013import org.openstreetmap.josm.data.validation.TestError; 014 015/** 016 * Check for missing name:* translations. 017 * <p> 018 * This test finds multilingual objects whose 'name' attribute is not 019 * equal to any 'name:*' attribute and not a composition of some 020 * 'name:*' attributes separated by ' - '. 021 * <p> 022 * For example, a node with name=Europe, name:de=Europa should have 023 * name:en=Europe to avoid triggering this test. An object with 024 * name='Suomi - Finland' should have at least name:fi=Suomi and 025 * name:sv=Finland to avoid a warning (name:et=Soome would not 026 * matter). Also, complain if an object has some name:* attribute but 027 * no name. 028 * 029 * @author Skela 030 */ 031public class NameMismatch extends Test { 032 protected static final int NAME_MISSING = 1501; 033 protected static final int NAME_TRANSLATION_MISSING = 1502; 034 035 /** 036 * Constructs a new {@code NameMismatch} test. 037 */ 038 public NameMismatch() { 039 super(tr("Missing name:* translation"), 040 tr("This test finds multilingual objects whose ''name'' attribute is not equal to some ''name:*'' attribute and not a composition of ''name:*'' attributes, e.g., Italia - Italien - Italy.")); 041 } 042 043 /** 044 * Report a missing translation. 045 * 046 * @param p The primitive whose translation is missing 047 */ 048 private void missingTranslation(OsmPrimitive p) { 049 errors.add(new TestError(this, Severity.OTHER, 050 tr("A name:* translation is missing."), 051 NAME_TRANSLATION_MISSING, p)); 052 } 053 054 /** 055 * Check a primitive for a name mismatch. 056 * 057 * @param p The primitive to be tested 058 */ 059 public void check(OsmPrimitive p) { 060 HashSet<String> names = new HashSet<String>(); 061 062 for (Entry<String, String> entry : p.getKeys().entrySet()) { 063 if (entry.getKey().startsWith("name:")) { 064 String n = entry.getValue(); 065 if (n != null) { 066 names.add(n); 067 } 068 } 069 } 070 071 if (names.isEmpty()) return; 072 073 String name = p.get("name"); 074 075 if (name == null) { 076 errors.add(new TestError(this, Severity.OTHER, 077 tr("A name is missing, even though name:* exists."), 078 NAME_MISSING, p)); 079 return; 080 } 081 082 if (names.contains(name)) return; 083 /* If name is not equal to one of the name:*, it should be a 084 composition of some (not necessarily all) name:* labels. 085 Check if this is the case. */ 086 087 String[] splitNames = name.split(" - "); 088 if (splitNames.length == 1) { 089 /* The name is not composed of multiple parts. Complain. */ 090 missingTranslation(p); 091 return; 092 } 093 094 /* Check that each part corresponds to a translated name:*. */ 095 for (String n : splitNames) { 096 if (!names.contains(n)) { 097 missingTranslation(p); 098 return; 099 } 100 } 101 } 102 103 /** 104 * Checks a name mismatch in all primitives. 105 * 106 * @param selection The primitives to be tested 107 */ 108 @Override 109 public void visit(Collection<OsmPrimitive> selection) { 110 for (OsmPrimitive p : selection) { 111 if (!p.isDeleted() && !p.isIncomplete()) { 112 check(p); 113 } 114 } 115 } 116}