001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import org.openstreetmap.josm.data.osm.Node;
008import org.openstreetmap.josm.data.osm.OsmPrimitive;
009import org.openstreetmap.josm.data.osm.Relation;
010import org.openstreetmap.josm.data.osm.Way;
011import org.openstreetmap.josm.data.validation.Severity;
012import org.openstreetmap.josm.data.validation.Test;
013import org.openstreetmap.josm.data.validation.TestError;
014import org.openstreetmap.josm.data.validation.routines.AbstractValidator;
015import org.openstreetmap.josm.data.validation.routines.EmailValidator;
016import org.openstreetmap.josm.data.validation.routines.UrlValidator;
017
018/**
019 * Performs validation tests on internet-related tags (websites, e-mail addresses, etc.).
020 * @since 7489
021 */
022public class InternetTags extends Test {
023
024    /** Error code for an invalid URL */
025    public static final int INVALID_URL = 3301;
026    /** Error code for an invalid e-mail */
027    public static final int INVALID_EMAIL = 3302;
028
029    /**
030     * List of keys subject to URL validation.
031     */
032    private static String[] URL_KEYS = new String[] {
033        "url", "source:url",
034        "website", "contact:website", "heritage:website", "source:website"
035    };
036
037    /**
038     * List of keys subject to email validation.
039     */
040    private static String[] EMAIL_KEYS = new String[] {
041        "email", "contact:email"
042    };
043
044    /**
045     * Constructs a new {@code InternetTags} test.
046     */
047    public InternetTags() {
048        super(tr("Internet tags"), tr("Checks for errors in internet-related tags."));
049    }
050
051    /**
052     * Potentially validates a given primitive key against a given validator.
053     * @param p The OSM primitive to test
054     * @param k The key to validate
055     * @param keys The list of keys to check. If {@code k} is not inside this collection, do nothing
056     * @param validator The validator to run if {@code k} is inside {@code keys}
057     * @param code The error code to set if the validation fails
058     * @return {@code true} if the validation fails. In this case, a new error has been created.
059     */
060    private boolean doTest(OsmPrimitive p, String k, String[] keys, AbstractValidator validator, int code) {
061        for (String i : keys) {
062            if (i.equals(k)) {
063                TestError error = validateTag(p, k, validator, code);
064                if (error != null) {
065                    errors.add(error);
066                }
067                break;
068            }
069        }
070        return false;
071    }
072
073    /**
074     * Validates a given primitive tag against a given validator.
075     * @param p The OSM primitive to test
076     * @param k The key to validate
077     * @param validator The validator to run
078     * @param code The error code to set if the validation fails
079     * @return The error if the validation fails, {@code null} otherwise
080     * @since 7824
081     */
082    public TestError validateTag(OsmPrimitive p, String k, AbstractValidator validator, int code) {
083        return doValidateTag(p, k, null, validator, code);
084    }
085
086    /**
087     * Validates a given primitive tag against a given validator.
088     * @param p The OSM primitive to test
089     * @param k The key to validate
090     * @param v The value to validate. May be {@code null} to use {@code p.get(k)}
091     * @param validator The validator to run
092     * @param code The error code to set if the validation fails
093     * @return The error if the validation fails, {@code null} otherwise
094     */
095    private TestError doValidateTag(OsmPrimitive p, String k, String v, AbstractValidator validator, int code) {
096        TestError error = null;
097        String value = v != null ? v : p.get(k);
098        if (!validator.isValid(value)) {
099            String errMsg = validator.getErrorMessage();
100            // Special treatment to allow URLs without protocol. See UrlValidator#isValid
101            if (tr("URL contains an invalid protocol: {0}", (String) null).equals(errMsg)) {
102                String proto = validator instanceof EmailValidator ? "mailto://" : "http://";
103                return doValidateTag(p, k, proto+value, validator, code);
104            }
105            error = TestError.builder(this, Severity.WARNING, code)
106                    .message(validator.getValidatorName(), marktr("''{0}'': {1}"), k, errMsg)
107                    .primitives(p)
108                    .build();
109        }
110        return error;
111    }
112
113    private void test(OsmPrimitive p) {
114        for (String k : p.keySet()) {
115            // Test key against URL validator
116            if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) {
117                // Test key against e-mail validator only if the URL validator did not fail
118                doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL);
119            }
120        }
121    }
122
123    @Override
124    public void visit(Node n) {
125        test(n);
126    }
127
128    @Override
129    public void visit(Way w) {
130        test(w);
131    }
132
133    @Override
134    public void visit(Relation r) {
135        test(r);
136    }
137}