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}