Index: trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 7486)
+++ trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 7489)
@@ -37,4 +37,5 @@
 import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
 import org.openstreetmap.josm.data.validation.tests.Highways;
+import org.openstreetmap.josm.data.validation.tests.InternetTags;
 import org.openstreetmap.josm.data.validation.tests.Lanes;
 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
@@ -121,4 +122,5 @@
         Lanes.class, // 3100 .. 3199
         ConditionalKeys.class, // 3200 .. 3299
+        InternetTags.class, // 3300 .. 3399
     };
 
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/AbstractValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/AbstractValidator.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/AbstractValidator.java	(revision 7489)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.routines;
+
+/**
+ * Abstract validator superclass to extend Apache Validator routines.
+ * @since 7489
+ */
+public abstract class AbstractValidator {
+
+    private String errorMessage;
+    private String fix;
+
+    /**
+     * Tests validity of a given value.
+     * @param value Value to test
+     * @return {@code true} if value is valid, {@code false} otherwise
+     */
+    public abstract boolean isValid(String value);
+
+    /**
+     * Replies the error message.
+     * @return the errorMessage
+     */
+    public final String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+     * Sets the error message.
+     * @param errorMessage the errorMessage
+     */
+    protected final void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    /**
+     * Replies the fixed value, if any.
+     * @return the fixed value or {@code null}
+     */
+    public final String getFix() {
+        return fix;
+    }
+
+    /**
+     * Sets the fixed value.
+     * @param fix the fixed value, if any
+     */
+    protected final void setFix(String fix) {
+        this.fix = fix;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java	(revision 7489)
@@ -0,0 +1,503 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openstreetmap.josm.data.validation.routines;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <p><b>Domain name</b> validation routines.</p>
+ *
+ * <p>
+ * This validator provides methods for validating Internet domain names
+ * and top-level domains.
+ * </p>
+ *
+ * <p>Domain names are evaluated according
+ * to the standards <a href="http://www.ietf.org/rfc/rfc1034.txt">RFC1034</a>,
+ * section 3, and <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC1123</a>,
+ * section 2.1. No accomodation is provided for the specialized needs of
+ * other applications; if the domain name has been URL-encoded, for example,
+ * validation will fail even though the equivalent plaintext version of the
+ * same name would have passed.
+ * </p>
+ *
+ * <p>
+ * Validation is also provided for top-level domains (TLDs) as defined and
+ * maintained by the Internet Assigned Numbers Authority (IANA):
+ * </p>
+ *
+ *   <ul>
+ *     <li>{@link #isValidInfrastructureTld} - validates infrastructure TLDs
+ *         (<code>.arpa</code>, etc.)</li>
+ *     <li>{@link #isValidGenericTld} - validates generic TLDs
+ *         (<code>.com, .org</code>, etc.)</li>
+ *     <li>{@link #isValidCountryCodeTld} - validates country code TLDs
+ *         (<code>.us, .uk, .cn</code>, etc.)</li>
+ *   </ul>
+ *
+ * <p>
+ * (<b>NOTE</b>: This class does not provide IP address lookup for domain names or
+ * methods to ensure that a given domain name matches a specific IP; see
+ * {@link java.net.InetAddress} for that functionality.)
+ * </p>
+ *
+ * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $
+ * @since Validator 1.4
+ */
+public class DomainValidator extends AbstractValidator {
+
+    // Regular expression strings for hostnames (derived from RFC2396 and RFC 1123)
+    private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]*\\p{Alnum})*";
+    private static final String TOP_LABEL_REGEX = "\\p{Alpha}{2,}";
+    private static final String DOMAIN_NAME_REGEX =
+            "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")$";
+
+    private final boolean allowLocal;
+
+    /**
+     * Singleton instance of this validator, which
+     *  doesn't consider local addresses as valid.
+     */
+    private static final DomainValidator DOMAIN_VALIDATOR = new DomainValidator(false);
+
+    /**
+     * Singleton instance of this validator, which does
+     *  consider local addresses valid.
+     */
+    private static final DomainValidator DOMAIN_VALIDATOR_WITH_LOCAL = new DomainValidator(true);
+
+    /**
+     * RegexValidator for matching domains.
+     */
+    private final RegexValidator domainRegex =
+            new RegexValidator(DOMAIN_NAME_REGEX);
+    /**
+     * RegexValidator for matching the a local hostname
+     */
+    private final RegexValidator hostnameRegex =
+            new RegexValidator(DOMAIN_LABEL_REGEX);
+
+    /**
+     * Returns the singleton instance of this validator. It
+     *  will not consider local addresses as valid.
+     * @return the singleton instance of this validator
+     */
+    public static DomainValidator getInstance() {
+        return DOMAIN_VALIDATOR;
+    }
+
+    /**
+     * Returns the singleton instance of this validator,
+     *  with local validation as required.
+     * @param allowLocal Should local addresses be considered valid?
+     * @return the singleton instance of this validator
+     */
+    public static DomainValidator getInstance(boolean allowLocal) {
+       if(allowLocal) {
+          return DOMAIN_VALIDATOR_WITH_LOCAL;
+       }
+       return DOMAIN_VALIDATOR;
+    }
+
+    /** Private constructor. */
+    private DomainValidator(boolean allowLocal) {
+       this.allowLocal = allowLocal;
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> parses
+     * as a valid domain name with a recognized top-level domain.
+     * The parsing is case-sensitive.
+     * @param domain the parameter to check for domain name syntax
+     * @return true if the parameter is a valid domain name
+     */
+    public boolean isValid(String domain) {
+        String[] groups = domainRegex.match(domain);
+        if (groups != null && groups.length > 0) {
+            return isValidTld(groups[0]);
+        } else if(allowLocal) {
+            if (hostnameRegex.isValid(domain)) {
+               return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> matches any
+     * IANA-defined top-level domain. Leading dots are ignored if present.
+     * The search is case-sensitive.
+     * @param tld the parameter to check for TLD status
+     * @return true if the parameter is a TLD
+     */
+    public boolean isValidTld(String tld) {
+        if(allowLocal && isValidLocalTld(tld)) {
+           return true;
+        }
+        return isValidInfrastructureTld(tld)
+                || isValidGenericTld(tld)
+                || isValidCountryCodeTld(tld);
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> matches any
+     * IANA-defined infrastructure top-level domain. Leading dots are
+     * ignored if present. The search is case-sensitive.
+     * @param iTld the parameter to check for infrastructure TLD status
+     * @return true if the parameter is an infrastructure TLD
+     */
+    public boolean isValidInfrastructureTld(String iTld) {
+        return INFRASTRUCTURE_TLD_LIST.contains(chompLeadingDot(iTld.toLowerCase()));
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> matches any
+     * IANA-defined generic top-level domain. Leading dots are ignored
+     * if present. The search is case-sensitive.
+     * @param gTld the parameter to check for generic TLD status
+     * @return true if the parameter is a generic TLD
+     */
+    public boolean isValidGenericTld(String gTld) {
+        return GENERIC_TLD_LIST.contains(chompLeadingDot(gTld.toLowerCase()));
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> matches any
+     * IANA-defined country code top-level domain. Leading dots are
+     * ignored if present. The search is case-sensitive.
+     * @param ccTld the parameter to check for country code TLD status
+     * @return true if the parameter is a country code TLD
+     */
+    public boolean isValidCountryCodeTld(String ccTld) {
+        return COUNTRY_CODE_TLD_LIST.contains(chompLeadingDot(ccTld.toLowerCase()));
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> matches any
+     * widely used "local" domains (localhost or localdomain). Leading dots are
+     *  ignored if present. The search is case-sensitive.
+     * @param iTld the parameter to check for local TLD status
+     * @return true if the parameter is an local TLD
+     */
+    public boolean isValidLocalTld(String iTld) {
+        return LOCAL_TLD_LIST.contains(chompLeadingDot(iTld.toLowerCase()));
+    }
+
+    private String chompLeadingDot(String str) {
+        if (str.startsWith(".")) {
+            return str.substring(1);
+        } else {
+            return str;
+        }
+    }
+
+    // ---------------------------------------------
+    // ----- TLDs defined by IANA
+    // ----- Authoritative and comprehensive list at:
+    // ----- http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+
+    private static final String[] INFRASTRUCTURE_TLDS = new String[] {
+        "arpa",               // internet infrastructure
+        "root"                // diagnostic marker for non-truncated root zone
+    };
+
+    private static final String[] GENERIC_TLDS = new String[] {
+        "aero",               // air transport industry
+        "asia",               // Pan-Asia/Asia Pacific
+        "biz",                // businesses
+        "cat",                // Catalan linguistic/cultural community
+        "com",                // commercial enterprises
+        "coop",               // cooperative associations
+        "info",               // informational sites
+        "jobs",               // Human Resource managers
+        "mobi",               // mobile products and services
+        "museum",             // museums, surprisingly enough
+        "name",               // individuals' sites
+        "net",                // internet support infrastructure/business
+        "org",                // noncommercial organizations
+        "pro",                // credentialed professionals and entities
+        "tel",                // contact data for businesses and individuals
+        "travel",             // entities in the travel industry
+        "gov",                // United States Government
+        "edu",                // accredited postsecondary US education entities
+        "mil",                // United States Military
+        "int"                 // organizations established by international treaty
+    };
+
+    private static final String[] COUNTRY_CODE_TLDS = new String[] {
+        "ac",                 // Ascension Island
+        "ad",                 // Andorra
+        "ae",                 // United Arab Emirates
+        "af",                 // Afghanistan
+        "ag",                 // Antigua and Barbuda
+        "ai",                 // Anguilla
+        "al",                 // Albania
+        "am",                 // Armenia
+        "an",                 // Netherlands Antilles
+        "ao",                 // Angola
+        "aq",                 // Antarctica
+        "ar",                 // Argentina
+        "as",                 // American Samoa
+        "at",                 // Austria
+        "au",                 // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands)
+        "aw",                 // Aruba
+        "ax",                 // Åland
+        "az",                 // Azerbaijan
+        "ba",                 // Bosnia and Herzegovina
+        "bb",                 // Barbados
+        "bd",                 // Bangladesh
+        "be",                 // Belgium
+        "bf",                 // Burkina Faso
+        "bg",                 // Bulgaria
+        "bh",                 // Bahrain
+        "bi",                 // Burundi
+        "bj",                 // Benin
+        "bm",                 // Bermuda
+        "bn",                 // Brunei Darussalam
+        "bo",                 // Bolivia
+        "br",                 // Brazil
+        "bs",                 // Bahamas
+        "bt",                 // Bhutan
+        "bv",                 // Bouvet Island
+        "bw",                 // Botswana
+        "by",                 // Belarus
+        "bz",                 // Belize
+        "ca",                 // Canada
+        "cc",                 // Cocos (Keeling) Islands
+        "cd",                 // Democratic Republic of the Congo (formerly Zaire)
+        "cf",                 // Central African Republic
+        "cg",                 // Republic of the Congo
+        "ch",                 // Switzerland
+        "ci",                 // Côte d'Ivoire
+        "ck",                 // Cook Islands
+        "cl",                 // Chile
+        "cm",                 // Cameroon
+        "cn",                 // China, mainland
+        "co",                 // Colombia
+        "cr",                 // Costa Rica
+        "cu",                 // Cuba
+        "cv",                 // Cape Verde
+        "cx",                 // Christmas Island
+        "cy",                 // Cyprus
+        "cz",                 // Czech Republic
+        "de",                 // Germany
+        "dj",                 // Djibouti
+        "dk",                 // Denmark
+        "dm",                 // Dominica
+        "do",                 // Dominican Republic
+        "dz",                 // Algeria
+        "ec",                 // Ecuador
+        "ee",                 // Estonia
+        "eg",                 // Egypt
+        "er",                 // Eritrea
+        "es",                 // Spain
+        "et",                 // Ethiopia
+        "eu",                 // European Union
+        "fi",                 // Finland
+        "fj",                 // Fiji
+        "fk",                 // Falkland Islands
+        "fm",                 // Federated States of Micronesia
+        "fo",                 // Faroe Islands
+        "fr",                 // France
+        "ga",                 // Gabon
+        "gb",                 // Great Britain (United Kingdom)
+        "gd",                 // Grenada
+        "ge",                 // Georgia
+        "gf",                 // French Guiana
+        "gg",                 // Guernsey
+        "gh",                 // Ghana
+        "gi",                 // Gibraltar
+        "gl",                 // Greenland
+        "gm",                 // The Gambia
+        "gn",                 // Guinea
+        "gp",                 // Guadeloupe
+        "gq",                 // Equatorial Guinea
+        "gr",                 // Greece
+        "gs",                 // South Georgia and the South Sandwich Islands
+        "gt",                 // Guatemala
+        "gu",                 // Guam
+        "gw",                 // Guinea-Bissau
+        "gy",                 // Guyana
+        "hk",                 // Hong Kong
+        "hm",                 // Heard Island and McDonald Islands
+        "hn",                 // Honduras
+        "hr",                 // Croatia (Hrvatska)
+        "ht",                 // Haiti
+        "hu",                 // Hungary
+        "id",                 // Indonesia
+        "ie",                 // Ireland (Éire)
+        "il",                 // Israel
+        "im",                 // Isle of Man
+        "in",                 // India
+        "io",                 // British Indian Ocean Territory
+        "iq",                 // Iraq
+        "ir",                 // Iran
+        "is",                 // Iceland
+        "it",                 // Italy
+        "je",                 // Jersey
+        "jm",                 // Jamaica
+        "jo",                 // Jordan
+        "jp",                 // Japan
+        "ke",                 // Kenya
+        "kg",                 // Kyrgyzstan
+        "kh",                 // Cambodia (Khmer)
+        "ki",                 // Kiribati
+        "km",                 // Comoros
+        "kn",                 // Saint Kitts and Nevis
+        "kp",                 // North Korea
+        "kr",                 // South Korea
+        "kw",                 // Kuwait
+        "ky",                 // Cayman Islands
+        "kz",                 // Kazakhstan
+        "la",                 // Laos (currently being marketed as the official domain for Los Angeles)
+        "lb",                 // Lebanon
+        "lc",                 // Saint Lucia
+        "li",                 // Liechtenstein
+        "lk",                 // Sri Lanka
+        "lr",                 // Liberia
+        "ls",                 // Lesotho
+        "lt",                 // Lithuania
+        "lu",                 // Luxembourg
+        "lv",                 // Latvia
+        "ly",                 // Libya
+        "ma",                 // Morocco
+        "mc",                 // Monaco
+        "md",                 // Moldova
+        "me",                 // Montenegro
+        "mg",                 // Madagascar
+        "mh",                 // Marshall Islands
+        "mk",                 // Republic of Macedonia
+        "ml",                 // Mali
+        "mm",                 // Myanmar
+        "mn",                 // Mongolia
+        "mo",                 // Macau
+        "mp",                 // Northern Mariana Islands
+        "mq",                 // Martinique
+        "mr",                 // Mauritania
+        "ms",                 // Montserrat
+        "mt",                 // Malta
+        "mu",                 // Mauritius
+        "mv",                 // Maldives
+        "mw",                 // Malawi
+        "mx",                 // Mexico
+        "my",                 // Malaysia
+        "mz",                 // Mozambique
+        "na",                 // Namibia
+        "nc",                 // New Caledonia
+        "ne",                 // Niger
+        "nf",                 // Norfolk Island
+        "ng",                 // Nigeria
+        "ni",                 // Nicaragua
+        "nl",                 // Netherlands
+        "no",                 // Norway
+        "np",                 // Nepal
+        "nr",                 // Nauru
+        "nu",                 // Niue
+        "nz",                 // New Zealand
+        "om",                 // Oman
+        "pa",                 // Panama
+        "pe",                 // Peru
+        "pf",                 // French Polynesia With Clipperton Island
+        "pg",                 // Papua New Guinea
+        "ph",                 // Philippines
+        "pk",                 // Pakistan
+        "pl",                 // Poland
+        "pm",                 // Saint-Pierre and Miquelon
+        "pn",                 // Pitcairn Islands
+        "pr",                 // Puerto Rico
+        "ps",                 // Palestinian territories (PA-controlled West Bank and Gaza Strip)
+        "pt",                 // Portugal
+        "pw",                 // Palau
+        "py",                 // Paraguay
+        "qa",                 // Qatar
+        "re",                 // Réunion
+        "ro",                 // Romania
+        "rs",                 // Serbia
+        "ru",                 // Russia
+        "rw",                 // Rwanda
+        "sa",                 // Saudi Arabia
+        "sb",                 // Solomon Islands
+        "sc",                 // Seychelles
+        "sd",                 // Sudan
+        "se",                 // Sweden
+        "sg",                 // Singapore
+        "sh",                 // Saint Helena
+        "si",                 // Slovenia
+        "sj",                 // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no)
+        "sk",                 // Slovakia
+        "sl",                 // Sierra Leone
+        "sm",                 // San Marino
+        "sn",                 // Senegal
+        "so",                 // Somalia
+        "sr",                 // Suriname
+        "st",                 // São Tomé and Príncipe
+        "su",                 // Soviet Union (deprecated)
+        "sv",                 // El Salvador
+        "sy",                 // Syria
+        "sz",                 // Swaziland
+        "tc",                 // Turks and Caicos Islands
+        "td",                 // Chad
+        "tf",                 // French Southern and Antarctic Lands
+        "tg",                 // Togo
+        "th",                 // Thailand
+        "tj",                 // Tajikistan
+        "tk",                 // Tokelau
+        "tl",                 // East Timor (deprecated old code)
+        "tm",                 // Turkmenistan
+        "tn",                 // Tunisia
+        "to",                 // Tonga
+        "tp",                 // East Timor
+        "tr",                 // Turkey
+        "tt",                 // Trinidad and Tobago
+        "tv",                 // Tuvalu
+        "tw",                 // Taiwan, Republic of China
+        "tz",                 // Tanzania
+        "ua",                 // Ukraine
+        "ug",                 // Uganda
+        "uk",                 // United Kingdom
+        "um",                 // United States Minor Outlying Islands
+        "us",                 // United States of America
+        "uy",                 // Uruguay
+        "uz",                 // Uzbekistan
+        "va",                 // Vatican City State
+        "vc",                 // Saint Vincent and the Grenadines
+        "ve",                 // Venezuela
+        "vg",                 // British Virgin Islands
+        "vi",                 // U.S. Virgin Islands
+        "vn",                 // Vietnam
+        "vu",                 // Vanuatu
+        "wf",                 // Wallis and Futuna
+        "ws",                 // Samoa (formerly Western Samoa)
+        "ye",                 // Yemen
+        "yt",                 // Mayotte
+        "yu",                 // Serbia and Montenegro (originally Yugoslavia)
+        "za",                 // South Africa
+        "zm",                 // Zambia
+        "zw",                 // Zimbabwe
+    };
+
+    private static final String[] LOCAL_TLDS = new String[] {
+       "localhost",           // RFC2606 defined
+       "localdomain"          // Also widely used as localhost.localdomain
+   };
+
+    private static final List<String> INFRASTRUCTURE_TLD_LIST = Arrays.asList(INFRASTRUCTURE_TLDS);
+    private static final List<String> GENERIC_TLD_LIST = Arrays.asList(GENERIC_TLDS);
+    private static final List<String> COUNTRY_CODE_TLD_LIST = Arrays.asList(COUNTRY_CODE_TLDS);
+    private static final List<String> LOCAL_TLD_LIST = Arrays.asList(LOCAL_TLDS);
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/EmailValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/EmailValidator.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/EmailValidator.java	(revision 7489)
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openstreetmap.josm.data.validation.routines;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>Perform email validations.</p>
+ * <p>
+ * This class is a Singleton; you can retrieve the instance via the getInstance() method.
+ * </p>
+ * <p>
+ * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
+ * http://javascript.internet.com
+ * </p>
+ * <p>
+ * This implementation is not guaranteed to catch all possible errors in an email address.
+ * For example, an address like nobody@noplace.somedog will pass validator, even though there
+ * is no TLD "somedog"
+ * </p>.
+ *
+ * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $
+ * @since Validator 1.4
+ */
+public class EmailValidator extends AbstractValidator {
+
+    private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
+    private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]";
+    private static final String QUOTED_USER = "(\"[^\"]*\")";
+    private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
+
+    private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$";
+    private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$";
+    private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
+    private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$";
+
+    private static final Pattern MATCH_ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX);
+    private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
+    private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
+    private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
+
+    private final boolean allowLocal;
+
+    /**
+     * Singleton instance of this class, which
+     *  doesn't consider local addresses as valid.
+     */
+    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false);
+
+    /**
+     * Singleton instance of this class, which does
+     *  consider local addresses valid.
+     */
+    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true);
+
+    /**
+     * Returns the Singleton instance of this validator.
+     *
+     * @return singleton instance of this validator.
+     */
+    public static EmailValidator getInstance() {
+        return EMAIL_VALIDATOR;
+    }
+
+    /**
+     * Returns the Singleton instance of this validator,
+     *  with local validation as required.
+     *
+     * @param allowLocal Should local addresses be considered valid?
+     * @return singleton instance of this validator
+     */
+    public static EmailValidator getInstance(boolean allowLocal) {
+        if(allowLocal) {
+           return EMAIL_VALIDATOR_WITH_LOCAL;
+        }
+        return EMAIL_VALIDATOR;
+    }
+
+    /**
+     * Protected constructor for subclasses to use.
+     *
+     * @param allowLocal Should local addresses be considered valid?
+     */
+    protected EmailValidator(boolean allowLocal) {
+        super();
+        this.allowLocal = allowLocal;
+    }
+
+    /**
+     * <p>Checks if a field has a valid e-mail address.</p>
+     *
+     * @param email The value validation is being performed on.  A <code>null</code>
+     *              value is considered invalid.
+     * @return true if the email address is valid.
+     */
+    @Override
+    public boolean isValid(String email) {
+        if (email == null) {
+            return false;
+        }
+
+        Matcher asciiMatcher = MATCH_ASCII_PATTERN.matcher(email);
+        if (!asciiMatcher.matches()) {
+            setErrorMessage(tr("E-mail address contains non-ascii characters"));
+            setFix(email.replaceAll("[^\\p{ASCII}]+", ""));
+            return false;
+        }
+
+        // Check the whole email address structure
+        Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
+        if (!emailMatcher.matches()) {
+            setErrorMessage(tr("E-mail address is invalid"));
+            return false;
+        }
+
+        if (email.endsWith(".")) {
+            setErrorMessage(tr("E-mail address is invalid"));
+            return false;
+        }
+
+        String username = emailMatcher.group(1);
+        if (!isValidUser(username)) {
+            setErrorMessage(tr("E-mail address contains an invalid username: {0}", username));
+            return false;
+        }
+
+        String domain = emailMatcher.group(2);
+        if (!isValidDomain(domain)) {
+            setErrorMessage(tr("E-mail address contains an invalid domain: {0}", domain));
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the domain component of an email address is valid.
+     *
+     * @param domain being validated.
+     * @return true if the email address's domain is valid.
+     */
+    protected boolean isValidDomain(String domain) {
+        // see if domain is an IP address in brackets
+        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
+
+        if (ipDomainMatcher.matches()) {
+            InetAddressValidator inetAddressValidator =
+                    InetAddressValidator.getInstance();
+            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
+        } else {
+            // Domain is symbolic name
+            DomainValidator domainValidator =
+                    DomainValidator.getInstance(allowLocal);
+            return domainValidator.isValid(domain);
+        }
+    }
+
+    /**
+     * Returns true if the user component of an email address is valid.
+     *
+     * @param user being validated
+     * @return true if the user name is valid.
+     */
+    protected boolean isValidUser(String user) {
+        return USER_PATTERN.matcher(user).matches();
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/InetAddressValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/InetAddressValidator.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/InetAddressValidator.java	(revision 7489)
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openstreetmap.josm.data.validation.routines;
+
+/**
+ * <p><b>InetAddress</b> validation and conversion routines (<code>java.net.InetAddress</code>).</p>
+ *
+ * <p>This class provides methods to validate a candidate IP address.
+ *
+ * <p>
+ * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method.
+ * </p>
+ *
+ * @version $Revision: 1227719 $
+ * @since Validator 1.4
+ */
+public class InetAddressValidator extends AbstractValidator {
+
+    private static final String IPV4_REGEX =
+            "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
+
+    /**
+     * Singleton instance of this class.
+     */
+    private static final InetAddressValidator VALIDATOR = new InetAddressValidator();
+
+    /** IPv4 RegexValidator */
+    private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX);
+
+    /**
+     * Returns the singleton instance of this validator.
+     * @return the singleton instance of this validator
+     */
+    public static InetAddressValidator getInstance() {
+        return VALIDATOR;
+    }
+
+    /**
+     * Checks if the specified string is a valid IP address.
+     * @param inetAddress the string to validate
+     * @return true if the string validates as an IP address
+     */
+    public boolean isValid(String inetAddress) {
+        return isValidInet4Address(inetAddress);
+    }
+
+    /**
+     * Validates an IPv4 address. Returns true if valid.
+     * @param inet4Address the IPv4 address to validate
+     * @return true if the argument contains a valid IPv4 address
+     */
+    public boolean isValidInet4Address(String inet4Address) {
+        // verify that address conforms to generic IPv4 format
+        String[] groups = ipv4Validator.match(inet4Address);
+
+        if (groups == null) return false;
+
+        // verify that address subgroups are legal
+        for (int i = 0; i <= 3; i++) {
+            String ipSegment = groups[i];
+            if (ipSegment == null || ipSegment.length() <= 0) {
+                return false;
+            }
+
+            int iIpSegment = 0;
+
+            try {
+                iIpSegment = Integer.parseInt(ipSegment);
+            } catch(NumberFormatException e) {
+                return false;
+            }
+
+            if (iIpSegment > 255) {
+                return false;
+            }
+
+        }
+
+        return true;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/RegexValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/RegexValidator.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/RegexValidator.java	(revision 7489)
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openstreetmap.josm.data.validation.routines;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <b>Regular Expression</b> validation (using JDK 1.4+ regex support).
+ * <p>
+ * Construct the validator either for a single regular expression or a set (array) of
+ * regular expressions. By default validation is <i>case sensitive</i> but constructors
+ * are provided to allow  <i>case in-sensitive</i> validation. For example to create
+ * a validator which does <i>case in-sensitive</i> validation for a set of regular
+ * expressions:
+ * <pre>
+ *         String[] regexs = new String[] {...};
+ *         RegexValidator validator = new RegexValidator(regexs, false);
+ * </pre>
+ * <p>
+ * <ul>
+ *   <li>Validate <code>true</code> or <code>false</code>:</li>
+ *   <ul>
+ *     <li><code>boolean valid = validator.isValid(value);</code></li>
+ *   </ul>
+ *   <li>Validate returning an aggregated String of the matched groups:</li>
+ *   <ul>
+ *     <li><code>String result = validator.validate(value);</code></li>
+ *   </ul>
+ *   <li>Validate returning the matched groups:</li>
+ *   <ul>
+ *     <li><code>String[] result = validator.match(value);</code></li>
+ *   </ul>
+ * </ul>
+ * <p>
+ * Cached instances pre-compile and re-use {@link Pattern}(s) - which according
+ * to the {@link Pattern} API are safe to use in a multi-threaded environment.
+ *
+ * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $
+ * @since Validator 1.4
+ */
+public class RegexValidator extends AbstractValidator {
+
+    private final Pattern[] patterns;
+
+    /**
+     * Construct a <i>case sensitive</i> validator for a single
+     * regular expression.
+     *
+     * @param regex The regular expression this validator will
+     * validate against
+     */
+    public RegexValidator(String regex) {
+        this(regex, true);
+    }
+
+    /**
+     * Construct a validator for a single regular expression
+     * with the specified case sensitivity.
+     *
+     * @param regex The regular expression this validator will
+     * validate against
+     * @param caseSensitive when <code>true</code> matching is <i>case
+     * sensitive</i>, otherwise matching is <i>case in-sensitive</i>
+     */
+    public RegexValidator(String regex, boolean caseSensitive) {
+        this(new String[] {regex}, caseSensitive);
+    }
+
+    /**
+     * Construct a <i>case sensitive</i> validator that matches any one
+     * of the set of regular expressions.
+     *
+     * @param regexs The set of regular expressions this validator will
+     * validate against
+     */
+    public RegexValidator(String[] regexs) {
+        this(regexs, true);
+    }
+
+    /**
+     * Construct a validator that matches any one of the set of regular
+     * expressions with the specified case sensitivity.
+     *
+     * @param regexs The set of regular expressions this validator will
+     * validate against
+     * @param caseSensitive when <code>true</code> matching is <i>case
+     * sensitive</i>, otherwise matching is <i>case in-sensitive</i>
+     */
+    public RegexValidator(String[] regexs, boolean caseSensitive) {
+        if (regexs == null || regexs.length == 0) {
+            throw new IllegalArgumentException("Regular expressions are missing");
+        }
+        patterns = new Pattern[regexs.length];
+        int flags =  (caseSensitive ? 0: Pattern.CASE_INSENSITIVE);
+        for (int i = 0; i < regexs.length; i++) {
+            if (regexs[i] == null || regexs[i].length() == 0) {
+                throw new IllegalArgumentException("Regular expression[" + i + "] is missing");
+            }
+            patterns[i] =  Pattern.compile(regexs[i], flags);
+        }
+    }
+
+    /**
+     * Validate a value against the set of regular expressions.
+     *
+     * @param value The value to validate.
+     * @return <code>true</code> if the value is valid
+     * otherwise <code>false</code>.
+     */
+    public boolean isValid(String value) {
+        if (value == null) {
+            return false;
+        }
+        for (int i = 0; i < patterns.length; i++) {
+            if (patterns[i].matcher(value).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Validate a value against the set of regular expressions
+     * returning the array of matched groups.
+     *
+     * @param value The value to validate.
+     * @return String array of the <i>groups</i> matched if
+     * valid or <code>null</code> if invalid
+     */
+    public String[] match(String value) {
+        if (value == null) {
+            return null;
+        }
+        for (int i = 0; i < patterns.length; i++) {
+            Matcher matcher = patterns[i].matcher(value);
+            if (matcher.matches()) {
+                int count = matcher.groupCount();
+                String[] groups = new String[count];
+                for (int j = 0; j < count; j++) {
+                    groups[j] = matcher.group(j+1);
+                }
+                return groups;
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Validate a value against the set of regular expressions
+     * returning a String value of the aggregated groups.
+     *
+     * @param value The value to validate.
+     * @return Aggregated String value comprised of the
+     * <i>groups</i> matched if valid or <code>null</code> if invalid
+     */
+    public String validate(String value) {
+        if (value == null) {
+            return null;
+        }
+        for (int i = 0; i < patterns.length; i++) {
+            Matcher matcher = patterns[i].matcher(value);
+            if (matcher.matches()) {
+                int count = matcher.groupCount();
+                if (count == 1) {
+                    return matcher.group(1);
+                }
+                StringBuffer buffer = new StringBuffer();
+                for (int j = 0; j < count; j++) {
+                    String component = matcher.group(j+1);
+                    if (component != null) {
+                        buffer.append(component);
+                    }
+                }
+                return buffer.toString();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Provide a String representation of this validator.
+     * @return A String representation of this validator
+     */
+    @Override
+    public String toString() {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append("RegexValidator{");
+        for (int i = 0; i < patterns.length; i++) {
+            if (i > 0) {
+                buffer.append(",");
+            }
+            buffer.append(patterns[i].pattern());
+        }
+        buffer.append("}");
+        return buffer.toString();
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/UrlValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/UrlValidator.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/UrlValidator.java	(revision 7489)
@@ -0,0 +1,512 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openstreetmap.josm.data.validation.routines;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p><b>URL Validation</b> routines.</p>
+ * Behavior of validation is modified by passing in options:
+ * <li>ALLOW_2_SLASHES - [FALSE]  Allows double '/' characters in the path
+ * component.</li>
+ * <li>NO_FRAGMENT- [FALSE]  By default fragments are allowed, if this option is
+ * included then fragments are flagged as illegal.</li>
+ * <li>ALLOW_ALL_SCHEMES - [FALSE] By default only http, https, and ftp are
+ * considered valid schemes.  Enabling this option will let any scheme pass validation.</li>
+ *
+ * <p>Originally based in on php script by Debbie Dyer, validation.php v1.2b, Date: 03/07/02,
+ * http://javascript.internet.com. However, this validation now bears little resemblance
+ * to the php original.</p>
+ * <pre>
+ *   Example of usage:
+ *   Construct a UrlValidator with valid schemes of "http", and "https".
+ *
+ *    String[] schemes = {"http","https"}.
+ *    UrlValidator urlValidator = new UrlValidator(schemes);
+ *    if (urlValidator.isValid("ftp://foo.bar.com/")) {
+ *       System.out.println("url is valid");
+ *    } else {
+ *       System.out.println("url is invalid");
+ *    }
+ *
+ *    prints "url is invalid"
+ *   If instead the default constructor is used.
+ *
+ *    UrlValidator urlValidator = new UrlValidator();
+ *    if (urlValidator.isValid("ftp://foo.bar.com/")) {
+ *       System.out.println("url is valid");
+ *    } else {
+ *       System.out.println("url is invalid");
+ *    }
+ *
+ *   prints out "url is valid"
+ *  </pre>
+ *
+ * @see
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">
+ *  Uniform Resource Identifiers (URI): Generic Syntax
+ * </a>
+ *
+ * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $
+ * @since Validator 1.4
+ */
+public class UrlValidator extends AbstractValidator {
+
+    /**
+     * Allows all validly formatted schemes to pass validation instead of
+     * supplying a set of valid schemes.
+     */
+    public static final long ALLOW_ALL_SCHEMES = 1 << 0;
+
+    /**
+     * Allow two slashes in the path component of the URL.
+     */
+    public static final long ALLOW_2_SLASHES = 1 << 1;
+
+    /**
+     * Enabling this options disallows any URL fragments.
+     */
+    public static final long NO_FRAGMENTS = 1 << 2;
+
+    /**
+     * Allow local URLs, such as http://localhost/ or http://machine/ .
+     * This enables a broad-brush check, for complex local machine name
+     *  validation requirements you should create your validator with
+     *  a {@link RegexValidator} instead ({@link #UrlValidator(RegexValidator, long)})
+     */
+    public static final long ALLOW_LOCAL_URLS = 1 << 3;
+
+    // Drop numeric, and  "+-." for now
+    private static final String AUTHORITY_CHARS_REGEX = "\\p{Alnum}\\-\\.";
+
+    /**
+     * This expression derived/taken from the BNF for URI (RFC2396).
+     */
+    private static final String URL_REGEX =
+            "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?";
+
+    private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX);
+
+    /**
+     * Schema/Protocol (ie. http:, ftp:, file:, etc).
+     */
+    private static final int PARSE_URL_SCHEME = 2;
+
+    /**
+     * Includes hostname/ip and port number.
+     */
+    private static final int PARSE_URL_AUTHORITY = 4;
+
+    private static final int PARSE_URL_PATH = 5;
+
+    private static final int PARSE_URL_QUERY = 7;
+
+    private static final int PARSE_URL_FRAGMENT = 9;
+
+    /**
+     * Protocol (ie. http:, ftp:,https:).
+     */
+    private static final String SCHEME_REGEX = "^\\p{Alpha}[\\p{Alnum}\\+\\-\\.]*";
+    private static final Pattern SCHEME_PATTERN = Pattern.compile(SCHEME_REGEX);
+
+    private static final String AUTHORITY_REGEX =
+            "^([" + AUTHORITY_CHARS_REGEX + "]*)(:\\d*)?(.*)?";
+
+    private static final Pattern AUTHORITY_PATTERN = Pattern.compile(AUTHORITY_REGEX);
+
+    private static final int PARSE_AUTHORITY_HOST_IP = 1;
+
+    private static final int PARSE_AUTHORITY_PORT = 2;
+
+    /**
+     * Should always be empty.
+     */
+    private static final int PARSE_AUTHORITY_EXTRA = 3;
+
+    private static final String PATH_REGEX = "^(/[-\\w:@&?=+,.!/~*'%$_;\\(\\)]*)?$";
+    private static final Pattern PATH_PATTERN = Pattern.compile(PATH_REGEX);
+
+    private static final String QUERY_REGEX = "^(.*)$";
+    private static final Pattern QUERY_PATTERN = Pattern.compile(QUERY_REGEX);
+
+    private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$";
+    private static final Pattern ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX);
+
+    private static final String PORT_REGEX = "^:(\\d{1,5})$";
+    private static final Pattern PORT_PATTERN = Pattern.compile(PORT_REGEX);
+
+    /**
+     * Holds the set of current validation options.
+     */
+    private final long options;
+
+    /**
+     * The set of schemes that are allowed to be in a URL.
+     */
+    private final Set<String> allowedSchemes;
+
+    /**
+     * Regular expressions used to manually validate authorities if IANA
+     * domain name validation isn't desired.
+     */
+    private final RegexValidator authorityValidator;
+
+    /**
+     * If no schemes are provided, default to this set.
+     */
+    private static final String[] DEFAULT_SCHEMES = {"http", "https", "ftp"};
+
+    /**
+     * Singleton instance of this class with default schemes and options.
+     */
+    private static final UrlValidator DEFAULT_URL_VALIDATOR = new UrlValidator();
+
+    /**
+     * Returns the singleton instance of this class with default schemes and options.
+     * @return singleton instance with default schemes and options
+     */
+    public static UrlValidator getInstance() {
+        return DEFAULT_URL_VALIDATOR;
+    }
+
+    /**
+     * Create a UrlValidator with default properties.
+     */
+    public UrlValidator() {
+        this(null);
+    }
+
+    /**
+     * Behavior of validation is modified by passing in several strings options:
+     * @param schemes Pass in one or more url schemes to consider valid, passing in
+     *        a null will default to "http,https,ftp" being valid.
+     *        If a non-null schemes is specified then all valid schemes must
+     *        be specified. Setting the ALLOW_ALL_SCHEMES option will
+     *        ignore the contents of schemes.
+     */
+    public UrlValidator(String[] schemes) {
+        this(schemes, 0L);
+    }
+
+    /**
+     * Initialize a UrlValidator with the given validation options.
+     * @param options The options should be set using the public constants declared in
+     * this class.  To set multiple options you simply add them together.  For example,
+     * ALLOW_2_SLASHES + NO_FRAGMENTS enables both of those options.
+     */
+    public UrlValidator(long options) {
+        this(null, null, options);
+    }
+
+    /**
+     * Behavior of validation is modified by passing in options:
+     * @param schemes The set of valid schemes.
+     * @param options The options should be set using the public constants declared in
+     * this class.  To set multiple options you simply add them together.  For example,
+     * ALLOW_2_SLASHES + NO_FRAGMENTS enables both of those options.
+     */
+    public UrlValidator(String[] schemes, long options) {
+        this(schemes, null, options);
+    }
+
+    /**
+     * Initialize a UrlValidator with the given validation options.
+     * @param authorityValidator Regular expression validator used to validate the authority part
+     * @param options Validation options. Set using the public constants of this class.
+     * To set multiple options, simply add them together:
+     * <p><code>ALLOW_2_SLASHES + NO_FRAGMENTS</code></p>
+     * enables both of those options.
+     */
+    public UrlValidator(RegexValidator authorityValidator, long options) {
+        this(null, authorityValidator, options);
+    }
+
+    /**
+     * Customizable constructor. Validation behavior is modifed by passing in options.
+     * @param schemes the set of valid schemes
+     * @param authorityValidator Regular expression validator used to validate the authority part
+     * @param options Validation options. Set using the public constants of this class.
+     * To set multiple options, simply add them together:
+     * <p><code>ALLOW_2_SLASHES + NO_FRAGMENTS</code></p>
+     * enables both of those options.
+     */
+    public UrlValidator(String[] schemes, RegexValidator authorityValidator, long options) {
+        this.options = options;
+
+        if (isOn(ALLOW_ALL_SCHEMES)) {
+            this.allowedSchemes = Collections.emptySet();
+        } else {
+            if (schemes == null) {
+                schemes = DEFAULT_SCHEMES;
+            }
+            this.allowedSchemes = new HashSet<>();
+            this.allowedSchemes.addAll(Arrays.asList(schemes));
+        }
+
+        this.authorityValidator = authorityValidator;
+
+    }
+
+    /**
+     * <p>Checks if a field has a valid url address.</p>
+     *
+     * @param value The value validation is being performed on.  A <code>null</code>
+     * value is considered invalid.
+     * @return true if the url is valid.
+     */
+    @Override
+    public boolean isValid(String value) {
+        if (value == null) {
+            return false;
+        }
+
+        if (!ASCII_PATTERN.matcher(value).matches()) {
+            setErrorMessage(tr("URL contains non-ascii characters"));
+            setFix(value.replaceAll("[^\\p{ASCII}]+", ""));
+            return false;
+        }
+
+        // Check the whole url address structure
+        Matcher urlMatcher = URL_PATTERN.matcher(value);
+        if (!urlMatcher.matches()) {
+            setErrorMessage(tr("URL is invalid"));
+            return false;
+        }
+
+        String scheme = urlMatcher.group(PARSE_URL_SCHEME);
+        if (!isValidScheme(scheme)) {
+            setErrorMessage(tr("URL contains an invalid protocol: {0}", scheme));
+            return false;
+        }
+
+        String authority = urlMatcher.group(PARSE_URL_AUTHORITY);
+        if ("file".equals(scheme) && "".equals(authority)) {
+           // Special case - file: allows an empty authority
+        } else {
+           // Validate the authority
+           if (!isValidAuthority(authority)) {
+               setErrorMessage(tr("URL contains an invalid authority: {0}", authority));
+               return false;
+            }
+        }
+
+        String path = urlMatcher.group(PARSE_URL_PATH);
+        if (!isValidPath(path)) {
+            setErrorMessage(tr("URL contains an invalid path: {0}", path));
+            return false;
+        }
+
+        String query = urlMatcher.group(PARSE_URL_QUERY);
+        if (!isValidQuery(query)) {
+            setErrorMessage(tr("URL contains an invalid query: {0}", query));
+            return false;
+        }
+
+        String fragment = urlMatcher.group(PARSE_URL_FRAGMENT);
+        if (!isValidFragment(fragment)) {
+            setErrorMessage(tr("URL contains an invalid fragment: {0}", fragment));
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Validate scheme. If schemes[] was initialized to a non null,
+     * then only those scheme's are allowed.  Note this is slightly different
+     * than for the constructor.
+     * @param scheme The scheme to validate.  A <code>null</code> value is considered
+     * invalid.
+     * @return true if valid.
+     */
+    protected boolean isValidScheme(String scheme) {
+        if (scheme == null) {
+            return false;
+        }
+
+        if (!SCHEME_PATTERN.matcher(scheme).matches()) {
+            return false;
+        }
+
+        if (isOff(ALLOW_ALL_SCHEMES)) {
+
+            if (!this.allowedSchemes.contains(scheme)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the authority is properly formatted.  An authority is the combination
+     * of hostname and port.  A <code>null</code> authority value is considered invalid.
+     * @param authority Authority value to validate.
+     * @return true if authority (hostname and port) is valid.
+     */
+    protected boolean isValidAuthority(String authority) {
+        if (authority == null) {
+            return false;
+        }
+
+        // check manual authority validation if specified
+        if (authorityValidator != null) {
+            if (authorityValidator.isValid(authority)) {
+                return true;
+            }
+        }
+
+        Matcher authorityMatcher = AUTHORITY_PATTERN.matcher(authority);
+        if (!authorityMatcher.matches()) {
+            return false;
+        }
+
+        String hostLocation = authorityMatcher.group(PARSE_AUTHORITY_HOST_IP);
+        // check if authority is hostname or IP address:
+        // try a hostname first since that's much more likely
+        DomainValidator domainValidator = DomainValidator.getInstance(isOn(ALLOW_LOCAL_URLS));
+        if (!domainValidator.isValid(hostLocation)) {
+            // try an IP address
+            InetAddressValidator inetAddressValidator =
+                InetAddressValidator.getInstance();
+            if (!inetAddressValidator.isValid(hostLocation)) {
+                // isn't either one, so the URL is invalid
+                return false;
+            }
+        }
+
+        String port = authorityMatcher.group(PARSE_AUTHORITY_PORT);
+        if (port != null) {
+            if (!PORT_PATTERN.matcher(port).matches()) {
+                return false;
+            }
+        }
+
+        String extra = authorityMatcher.group(PARSE_AUTHORITY_EXTRA);
+        if (extra != null && extra.trim().length() > 0){
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the path is valid.  A <code>null</code> value is considered invalid.
+     * @param path Path value to validate.
+     * @return true if path is valid.
+     */
+    protected boolean isValidPath(String path) {
+        if (path == null) {
+            return false;
+        }
+
+        if (!PATH_PATTERN.matcher(path).matches()) {
+            return false;
+        }
+
+        int slash2Count = countToken("//", path);
+        if (isOff(ALLOW_2_SLASHES) && (slash2Count > 0)) {
+            return false;
+        }
+
+        int slashCount = countToken("/", path);
+        int dot2Count = countToken("..", path);
+        if (dot2Count > 0) {
+            if ((slashCount - slash2Count - 1) <= dot2Count) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the query is null or it's a properly formatted query string.
+     * @param query Query value to validate.
+     * @return true if query is valid.
+     */
+    protected boolean isValidQuery(String query) {
+        if (query == null) {
+            return true;
+        }
+
+        return QUERY_PATTERN.matcher(query).matches();
+    }
+
+    /**
+     * Returns true if the given fragment is null or fragments are allowed.
+     * @param fragment Fragment value to validate.
+     * @return true if fragment is valid.
+     */
+    protected boolean isValidFragment(String fragment) {
+        if (fragment == null) {
+            return true;
+        }
+
+        return isOff(NO_FRAGMENTS);
+    }
+
+    /**
+     * Returns the number of times the token appears in the target.
+     * @param token Token value to be counted.
+     * @param target Target value to count tokens in.
+     * @return the number of tokens.
+     */
+    protected int countToken(String token, String target) {
+        int tokenIndex = 0;
+        int count = 0;
+        while (tokenIndex != -1) {
+            tokenIndex = target.indexOf(token, tokenIndex);
+            if (tokenIndex > -1) {
+                tokenIndex++;
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Tests whether the given flag is on.  If the flag is not a power of 2
+     * (ie. 3) this tests whether the combination of flags is on.
+     *
+     * @param flag Flag value to check.
+     *
+     * @return whether the specified flag value is on.
+     */
+    private boolean isOn(long flag) {
+        return (this.options & flag) > 0;
+    }
+
+    /**
+     * Tests whether the given flag is off.  If the flag is not a power of 2
+     * (ie. 3) this tests whether the combination of flags is off.
+     *
+     * @param flag Flag value to check.
+     *
+     * @return whether the specified flag value is off.
+     */
+    private boolean isOff(long flag) {
+        return (this.options & flag) == 0;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/validation/routines/package.html
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/package.html	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/package.html	(revision 7489)
@@ -0,0 +1,293 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<html>
+<head>
+<title>Package Documentation for org.openstreetmap.josm.data.validation.routines Package</title>
+</head>
+<body bgcolor="white">
+    <p>This package contains <i>independant</i> validation routines adapted from Apache Commons Validator 1.4.0.</p>
+<h1>Table of Contents</h1>
+
+<ul>
+<li>1. <a href="#overview">Overview</a>
+<li>2. <a href="#other">Validators</a>
+    <ul>
+    <li>2.1 <a href="#other.overview">Overview</a></li>
+    <li>2.2 <a href="#other.regex">Regular Expression validation</a></li>
+    <li>2.3 <a href="#other.inet">IP Address Validation</a></li>
+    <li>2.4 <a href="#other.email">Email Address Validation</a></li>
+    <li>2.5 <a href="#other.url">URL Validation</a></li>
+    <li>2.6 <a href="#other.domain">Domain Name Validation</a></li>
+    </ul></li>
+</ul>
+
+<a id="overview"></a>
+<h1>1. Overview</h1>
+<p>
+   Commons Validator serves two purposes:
+</p>
+    <ul>
+       <li>To provide standard, independant validation routines/functions.</li>
+       <li>To provide a <i>mini</i> framework for Validation.</li>
+    </ul>
+<p>
+   This package has been created, since version 1.3.0, in an attempt to clearly
+   separate these two concerns and is the location for the standard, independant
+   validation routines/functions in <em>Commons Validator</em>.
+</p>
+
+<p>
+   The contents of this package have no dependencies on the framework aspect of
+   Commons Validator and can be used on their own.
+</p>
+
+<a id="other"></a>
+<h1>2. Validators</h1>
+
+<a id="other.overview"></a>
+<h3>2.1 Overview</h3>
+<p>
+   This section lists other available validators.
+</p>
+<ul>
+   <li><a href="#other.regex">Regular Expressions</a> - validates
+       using Java 1.4+ regular expression support</li>
+   <li><a href="#other.inet">IP Address Validation</a> - provides IPv4 address
+       validation.</li>
+   <li><a href="#other.email">Email Address Validation</a> - provides email
+       address validation according to RFC 822 standards.</li>
+   <li><a href="#other.url">URL Validation</a> - provides URL validation on
+       scheme, domain, and authority.</li>
+   <li><a href="#other.domain">Domain Name Validation</a> - provides domain
+       name and IANA TLD validation.</li>
+</ul>
+
+<a id="other.regex"></a>
+<h3>2.2 Regular Expression Validation</h3>
+<p>
+   Regular expression validation can be done either by using the <i>static</i>
+   methods provied by <a href="RegexValidator.html">RegexValidator</a> or
+   by creating a new instance, which caches and re-uses compiled Patterns.
+</p>
+<ul>
+   <li><b>Method Flavours</b> - three <i>flavours</i> of validation metods are provided:</li>
+    <ul>
+        <li><code>isValid()</code> methods return true/false to indicate
+            whether validation was successful.</li>
+        <li><code>validate()</code> methods return a <code>String</code>
+            value of the matched <i>groups</i> aggregated together or
+            <code>null</code> if invalid.</li>
+        <li><code>match()</code> methods return a <code>String</code> array
+            of the matched <i>groups</i> or <code>null</code> if invalid.</li>
+    </ul>
+   <li><b>Case Sensitivity</b> - matching can be done in either a <i>case
+       sensitive</i> or <i>case in-sensitive</i> way.</li>
+   <li><b>Multiple Expressions</b> - instances of the
+       <a href="RegexValidator.html">RegexValidator</a>
+       can be created to either match against a single regular expression
+       or set (String array) of regular expressions.</li>
+</ul>
+<p>
+   Below is an example of using one of the static methods to validate,
+   matching in a <i>case insensitive</i> manner and returning a String
+   of the matched groups (which doesn't include the hyphen).
+</p>
+<pre>
+      // set up the parameters
+      boolean caseSensitive   = false;
+      String regex            = "^([A-Z]*)(?:\\-)([A-Z]*)$";
+
+      // validate - result should be a String of value "abcdef"
+      String result = RegexValidator.validate("abc-def", regex, caseSensitive);
+
+</pre>
+
+<p>The following static methods are provided for regular expression validation:
+</p>
+<ul>
+    <li><code>isValid(<i>value</i>, <i>regex</i>)</code></li>
+    <li><code>isValid(<i>value</i>, <i>regex</i>, <i>caseSensitive</i>)</code></li>
+    <li><code>validate(<i>value</i>, <i>regex</i>)</code></li>
+    <li><code>validate(<i>value</i>, <i>regex</i>, <i>caseSensitive</i>)</code></li>
+    <li><code>match(<i>value</i>, <i>regex</i>)</code></li>
+    <li><code>match(<i>value</i>, <i>regex</i>, <i>caseSensitive</i>)</code></li>
+</ul>
+<p>
+   Below is an example of creating an instance of
+   <a href="RegexValidator.html">RegexValidator</a> matching in a <i>case insensitive</i>
+   manner against a set of regular expressions:
+</p>
+<pre>
+      // set up the parameters
+      boolean caseSensitive = false;
+      String regex1   = "^([A-Z]*)(?:\\-)([A-Z]*)*$"
+      String regex2   = "^([A-Z]*)$";
+      String[] regexs = new String[] {regex1, regex1};
+
+      // Create the validator
+      RegexValidator validator = new RegexValidator(regexs, caseSensitive);
+
+      // Validate true/false
+      boolean valid = validator.isValid("abc-def");
+
+      // Validate and return a String
+      String result = validator.validate("abc-def");
+
+      // Validate and return a String[]
+      String[] groups = validator.match("abc-def");
+
+</pre>
+<p>See the
+   <a href="RegexValidator.html">RegexValidator</a> javadoc for a full list
+   of the available constructors.
+</p>
+
+<a id="other.inet"></a>
+<h3>2.3 IP Address Validation</h3>
+
+<p>
+    <a href="InetAddressValidator.html">InetAddressValidator</a> provides
+    IPv4 address validation.
+</p>
+<p>
+    For example:
+</p>
+<pre>
+
+      // Get an InetAddressValidator
+      InetAddressValidator validator = InetAddressValidator.getInstance();
+
+      // Validate an IPv4 address
+      if (!validator.isValid(candidateInetAddress)) {
+          ... // invalid
+      }
+
+</pre>
+
+<a id="other.email"></a>
+<h3>2.4 Email Address Validation</h3>
+
+<p>
+    <a href="EmailValidator.html">EmailValidator</a> provides email address
+    validation according to RFC 822 standards.
+</p>
+<p>
+    For example:
+</p>
+<pre>
+      // Get an EmailValidator
+      EmailValidator validator = EmailValidator.getInstance();
+
+      // Validate an email address
+      boolean isAddressValid = validator.isValid("user@apache.org");
+
+      // Validate a variable containing an email address
+      if (!validator.isValid(addressFromUserForm)) {
+          webController.sendRedirect(ERROR_REDIRECT, "Email address isn't valid");
+          // etc.
+      }
+</pre>
+
+<a id="other.url"></a>
+<h3>2.5 URL Validation</h3>
+
+<p>
+    <a href="UrlValidator.html">UrlValidator</a> provides URL validation by
+    checking the scheme, authority, path, query, and fragment in turn. Clients
+    may specify valid schemes to be used in validating in addition to or instead of
+    the default values (HTTP, HTTPS, FTP). The UrlValidator also supports options
+    that change the parsing rules; for example, the ALLOW_2_SLASHES option instructs
+    the Validator to allow consecutive slash characters in the path component, which
+    is considered an error by default.
+
+    For more information on the available options, see the UrlValidator documentation.
+</p>
+<p>
+    For example:
+</p>
+<pre>
+      // Get an UrlValidator
+      UrlValidator defaultValidator = new UrlValidator(); // default schemes
+      if (defaultValidator.isValid("http://www.apache.org")) {
+          ... // valid
+      }
+      if (!defaultValidator.isValid("http//www.oops.com")) {
+          ... // invalid
+      }
+
+      // Get an UrlValidator with custom schemes
+      String[] customSchemes = { "sftp", "scp", "https" };
+      UrlValidator customValidator = new UrlValidator(customSchemes);
+      if (!customValidator.isValid("http://www.apache.org")) {
+          ... // invalid due to insecure protocol
+      }
+
+      // Get an UrlValidator that allows double slashes in the path
+      UrlValidator doubleSlashValidator = new UrlValidator(UrlValidator.ALLOW_2_SLASHES);
+      if (doubleSlashValidator.isValid("http://www.apache.org//projects")) {
+          ... // valid only in this Validator instance
+      }
+</pre>
+
+<a id="other.domain"></a>
+<h3>2.6 Domain Name Validation</h3>
+
+<p>
+    <a href="DomainValidator.html">DomainValidator</a> provides validation of Internet
+    domain names as specified by RFC1034/RFC1123 and according to the IANA-recognized
+    list of top-level domains (TLDs). Clients may validate an entire domain name, a
+    TLD of any category, or a TLD within a specific category.
+</p>
+<p>
+    For example:
+</p>
+<pre>
+      // Get a DomainValidator
+      DomainValidator validator = DomainValidator.getInstance();
+
+      // Validate a domain name
+      if (validator.isValid("www.apache.org")) {
+          ... // valid
+      }
+      if (!validator.isValid("www.apache.wrong")) {
+          ... // invalid
+      }
+
+      // Validate a TLD
+      if (validator.isValidTld(".com")) {
+          ... // valid
+      }
+      if (validator.isValidTld("org")) {
+          ... // valid, the leading dot is optional
+      }
+      if (validator.isValidTld(".us")) {
+          ... // valid, country code TLDs are also accepted
+      }
+
+      // Validate TLDs in categories
+      if (validator.isValidGenericTld(".name")) {
+          ... // valid
+      }
+      if (!validator.isValidGenericTld(".uk")) {
+          ... // invalid, .uk is a country code TLD
+      }
+      if (!validator.isValidCountryCodeTld(".info")) {
+          ... // invalid, .info is a generic TLD
+      }
+</pre>
+</body>
+</html>
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/InternetTags.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/InternetTags.java	(revision 7489)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/InternetTags.java	(revision 7489)
@@ -0,0 +1,93 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.validation.FixableTestError;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.data.validation.routines.AbstractValidator;
+import org.openstreetmap.josm.data.validation.routines.EmailValidator;
+import org.openstreetmap.josm.data.validation.routines.UrlValidator;
+
+/**
+ * Performs validation tests on internet-related tags (websites, e-mail addresses, etc.).
+ * @since 7489
+ */
+public class InternetTags extends Test {
+
+    protected static final int INVALID_URL = 3301;
+    protected static final int INVALID_EMAIL = 3302;
+
+    /**
+     * List of keys subject to URL validation.
+     */
+    public static String[] URL_KEYS = new String[] {
+        "url", "source:url",
+        "website", "contact:website", "heritage:website", "source:website"
+    };
+
+    /**
+     * List of keys subject to email validation.
+     */
+    public static String[] EMAIL_KEYS = new String[] {
+        "email", "contact:email"
+    };
+
+    /**
+     * Constructs a new {@code InternetTags} test.
+     */
+    public InternetTags() {
+        super(tr("Internet tags"), tr("Checks for errors in internet-related tags."));
+    }
+
+    private boolean doTest(OsmPrimitive p, String k, String[] keys, AbstractValidator validator, int code) {
+        for (String i : keys) {
+            if (i.equals(k)) {
+                if (!validator.isValid(p.get(k))) {
+                    TestError error;
+                    String msg = tr("''{0}'': {1}", k, validator.getErrorMessage());
+                    String fix = validator.getFix();
+                    if (fix != null) {
+                        error = new FixableTestError(this, Severity.WARNING, msg, code, p,
+                                new ChangePropertyCommand(p, k, fix));
+                    } else {
+                        error = new TestError(this, Severity.WARNING, msg, code, p);
+                    }
+                    return errors.add(error);
+                }
+                break;
+            }
+        }
+        return false;
+    }
+
+    private void test(OsmPrimitive p) {
+        for (String k : p.keySet()) {
+            if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) {
+                doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL);
+            }
+        }
+    }
+
+    @Override
+    public void visit(Node n) {
+        test(n);
+    }
+
+    @Override
+    public void visit(Way w) {
+        test(w);
+    }
+
+    @Override
+    public void visit(Relation r) {
+        test(r);
+    }
+}
