Index: /trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java	(revision 9852)
+++ /trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java	(revision 9853)
@@ -17,4 +17,5 @@
 package org.openstreetmap.josm.data.validation.routines;
 
+import java.net.IDN;
 import java.util.Arrays;
 import java.util.Locale;
@@ -31,5 +32,5 @@
  * 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
+ * section 2.1. No accommodation 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
@@ -47,6 +48,4 @@
  *     <li>{@link #isValidGenericTld} - validates generic TLDs
  *         (<code>.com, .org</code>, etc.)</li>
- *     <li>{@link #isValidIdnTld} - validates IDN TLDs
- *         (<code>.xn--*</code>, etc.)</li>
  *     <li>{@link #isValidCountryCodeTld} - validates country code TLDs
  *         (<code>.us, .uk, .cn</code>, etc.)</li>
@@ -59,18 +58,30 @@
  * </p>
  *
- * @version $Revision: 1640271 $ $Date: 2014-11-18 02:32:15 2014 UTC (Tue, 18 Nov 2014) $
+ * @version $Revision: 1725571 $
  * @since Validator 1.4
  */
 public final class DomainValidator extends AbstractValidator {
 
+    private static final int MAX_DOMAIN_LENGTH = 253;
+
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
     // 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,}";
-    // JOSM PATCH BEGIN
-    // See #10862 - IDN TLDs in ASCII form
-    private static final String TOP_LABEL_IDN_REGEX = "(?:xn|XN)--\\p{Alnum}{2,}(?:-\\p{Alpha}{2,})?";
+
+    // RFC2396: domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
+    // Max 63 characters
+    private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?";
+
+    // RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+    // Max 63 characters
+    private static final String TOP_LABEL_REGEX = "\\p{Alpha}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?";
+
+    // RFC2396 hostname = *( domainlabel "." ) toplabel [ "." ]
+    // Note that the regex currently requires both a domain label and a top level label, whereas
+    // the RFC does not. This is because the regex is used to detect if a TLD is present.
+    // If the match fails, input is checked against DOMAIN_LABEL_REGEX (hostnameRegex)
+    // RFC1123 sec 2.1 allows hostnames to start with a digit
     private static final String DOMAIN_NAME_REGEX =
-            "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + "|" + TOP_LABEL_IDN_REGEX + ")$";
-    // JOSM PATCH END
+            "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")\\.?$";
 
     private final boolean allowLocal;
@@ -94,6 +105,7 @@
             new RegexValidator(DOMAIN_NAME_REGEX);
     /**
-     * RegexValidator for matching the a local hostname
-     */
+     * RegexValidator for matching a local hostname
+     */
+    // RFC1123 sec 2.1 allows hostnames to start with a digit
     private final RegexValidator hostnameRegex =
             new RegexValidator(DOMAIN_LABEL_REGEX);
@@ -104,5 +116,6 @@
      * @return the singleton instance of this validator
      */
-    public static DomainValidator getInstance() {
+    public static synchronized DomainValidator getInstance() {
+        inUse = true;
         return DOMAIN_VALIDATOR;
     }
@@ -114,9 +127,10 @@
      * @return the singleton instance of this validator
      */
-    public static DomainValidator getInstance(boolean allowLocal) {
-       if (allowLocal) {
-          return DOMAIN_VALIDATOR_WITH_LOCAL;
-       }
-       return DOMAIN_VALIDATOR;
+    public static synchronized DomainValidator getInstance(boolean allowLocal) {
+        inUse = true;
+        if (allowLocal) {
+            return DOMAIN_VALIDATOR_WITH_LOCAL;
+        }
+        return DOMAIN_VALIDATOR;
     }
 
@@ -126,5 +140,5 @@
      */
     private DomainValidator(boolean allowLocal) {
-       this.allowLocal = allowLocal;
+        this.allowLocal = allowLocal;
     }
 
@@ -132,5 +146,5 @@
      * 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.
+     * The parsing is case-insensitive.
      * @param domain the parameter to check for domain name syntax
      * @return true if the parameter is a valid domain name
@@ -138,13 +152,39 @@
     @Override
     public boolean isValid(String domain) {
+        if (domain == null) {
+            return false;
+        }
+        domain = unicodeToASCII(domain);
+        // hosts must be equally reachable via punycode and Unicode
+        // Unicode is never shorter than punycode, so check punycode
+        // if domain did not convert, then it will be caught by ASCII
+        // checks in the regexes below
+        if (domain.length() > MAX_DOMAIN_LENGTH) {
+            return false;
+        }
         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;
+        return allowLocal && hostnameRegex.isValid(domain);
+    }
+
+    // package protected for unit test access
+    // must agree with isValid() above
+    boolean isValidDomainSyntax(String domain) {
+        if (domain == null) {
+            return false;
+        }
+        domain = unicodeToASCII(domain);
+        // hosts must be equally reachable via punycode and Unicode
+        // Unicode is never shorter than punycode, so check punycode
+        // if domain did not convert, then it will be caught by ASCII
+        // checks in the regexes below
+        if (domain.length() > MAX_DOMAIN_LENGTH) {
+            return false;
+        }
+        String[] groups = domainRegex.match(domain);
+        return (groups != null && groups.length > 0)
+                || hostnameRegex.isValid(domain);
     }
 
@@ -152,15 +192,15 @@
      * 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
+     * The search is case-insensitive.
+     * @param tld the parameter to check for TLD status, not null
      * @return true if the parameter is a TLD
      */
     public boolean isValidTld(String tld) {
+        tld = unicodeToASCII(tld);
         if (allowLocal && isValidLocalTld(tld)) {
-           return true;
+            return true;
         }
         return isValidInfrastructureTld(tld)
                 || isValidGenericTld(tld)
-                || isValidIdnTld(tld)
                 || isValidCountryCodeTld(tld);
     }
@@ -169,10 +209,11 @@
      * 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
+     * ignored if present. The search is case-insensitive.
+     * @param iTld the parameter to check for infrastructure TLD status, not null
      * @return true if the parameter is an infrastructure TLD
      */
     public boolean isValidInfrastructureTld(String iTld) {
-        return Arrays.binarySearch(INFRASTRUCTURE_TLDS, chompLeadingDot(iTld.toLowerCase(Locale.ENGLISH))) >= 0;
+        final String key = chompLeadingDot(unicodeToASCII(iTld).toLowerCase(Locale.ENGLISH));
+        return arrayContains(INFRASTRUCTURE_TLDS, key);
     }
 
@@ -180,21 +221,12 @@
      * 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
+     * if present. The search is case-insensitive.
+     * @param gTld the parameter to check for generic TLD status, not null
      * @return true if the parameter is a generic TLD
      */
     public boolean isValidGenericTld(String gTld) {
-        return Arrays.binarySearch(GENERIC_TLDS, chompLeadingDot(gTld.toLowerCase(Locale.ENGLISH))) >= 0;
-    }
-
-    /**
-     * Returns true if the specified <code>String</code> matches any
-     * IANA-defined IDN top-level domain. Leading dots are ignored
-     * if present. The search is case-sensitive.
-     * @param iTld the parameter to check for IDN TLD status
-     * @return true if the parameter is an IDN TLD
-     */
-    public boolean isValidIdnTld(String iTld) {
-        return Arrays.binarySearch(IDN_TLDS, chompLeadingDot(iTld.toUpperCase(Locale.ENGLISH))) >= 0;
+        final String key = chompLeadingDot(unicodeToASCII(gTld).toLowerCase(Locale.ENGLISH));
+        return (arrayContains(GENERIC_TLDS, key) || arrayContains(genericTLDsPlus, key))
+                && !arrayContains(genericTLDsMinus, key);
     }
 
@@ -202,10 +234,12 @@
      * 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
+     * ignored if present. The search is case-insensitive.
+     * @param ccTld the parameter to check for country code TLD status, not null
      * @return true if the parameter is a country code TLD
      */
     public boolean isValidCountryCodeTld(String ccTld) {
-        return Arrays.binarySearch(COUNTRY_CODE_TLDS, chompLeadingDot(ccTld.toLowerCase(Locale.ENGLISH))) >= 0;
+        final String key = chompLeadingDot(unicodeToASCII(ccTld).toLowerCase(Locale.ENGLISH));
+        return (arrayContains(COUNTRY_CODE_TLDS, key) || arrayContains(countryCodeTLDsPlus, key))
+                && !arrayContains(countryCodeTLDsMinus, key);
     }
 
@@ -213,10 +247,11 @@
      * 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
+     * ignored if present. The search is case-insensitive.
+     * @param lTld the parameter to check for local TLD status, not null
      * @return true if the parameter is an local TLD
      */
-    public boolean isValidLocalTld(String iTld) {
-        return Arrays.binarySearch(LOCAL_TLDS, chompLeadingDot(iTld.toLowerCase(Locale.ENGLISH))) >= 0;
+    public boolean isValidLocalTld(String lTld) {
+        final String key = chompLeadingDot(unicodeToASCII(lTld).toLowerCase(Locale.ENGLISH));
+        return arrayContains(LOCAL_TLDS, key);
     }
 
@@ -224,7 +259,6 @@
         if (str.startsWith(".")) {
             return str.substring(1);
-        } else {
-            return str;
         }
+        return str;
     }
 
@@ -234,510 +268,965 @@
     // ----- http://data.iana.org/TLD/tlds-alpha-by-domain.txt
 
+    // Note that the above list is in UPPER case.
+    // The code currently converts strings to lower case (as per the tables below)
+
+    // IANA also provide an HTML list at http://www.iana.org/domains/root/db
+    // Note that this contains several country code entries which are NOT in
+    // the text file. These all have the "Not assigned" in the "Sponsoring Organisation" column
+    // For example (as of 2015-01-02):
+    // .bl  country-code    Not assigned
+    // .um  country-code    Not assigned
+
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
     private static final String[] INFRASTRUCTURE_TLDS = new String[] {
         "arpa",               // internet infrastructure
-        "root"                // diagnostic marker for non-truncated root zone
     };
 
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
     private static final String[] GENERIC_TLDS = new String[] {
-        "abogado",
-        "academy",
-        "accountants",
-        "active",
-        "actor",
-        "aero",
-        "agency",
-        "airforce",
-        "allfinanz",
-        "alsace",
-        "archi",
-        "army",
-        "arpa",
-        "asia",
-        "associates",
-        "attorney",
-        "auction",
-        "audio",
-        "autos",
-        "axa",
-        "band",
-        "bar",
-        "bargains",
-        "bayern",
-        "beer",
-        "berlin",
-        "best",
-        "bid",
-        "bike",
-        "bio",
-        "biz",
-        "black",
-        "blackfriday",
-        "blue",
-        "bmw",
-        "bnpparibas",
-        "boo",
-        "boutique",
-        "brussels",
-        "budapest",
-        "build",
-        "builders",
-        "business",
-        "buzz",
-        "bzh",
-        "cab",
-        "cal",
-        "camera",
-        "camp",
-        "cancerresearch",
-        "capetown",
-        "capital",
-        "caravan",
-        "cards",
-        "care",
-        "career",
-        "careers",
-        "casa",
-        "cash",
-        "cat",
-        "catering",
-        "center",
-        "ceo",
-        "cern",
-        "channel",
-        "cheap",
-        "christmas",
-        "chrome",
-        "church",
-        "citic",
-        "city",
-        "claims",
-        "cleaning",
-        "click",
-        "clinic",
-        "clothing",
-        "club",
-        "codes",
-        "coffee",
-        "college",
-        "cologne",
-        "com",
-        "community",
-        "company",
-        "computer",
-        "condos",
-        "construction",
-        "consulting",
-        "contractors",
-        "cooking",
-        "cool",
-        "coop",
-        "country",
-        "credit",
-        "creditcard",
-        "crs",
-        "cruises",
-        "cuisinella",
-        "cymru",
-        "dad",
-        "dance",
-        "dating",
-        "day",
-        "deals",
-        "degree",
-        "democrat",
-        "dental",
-        "dentist",
-        "desi",
-        "diamonds",
-        "diet",
-        "digital",
-        "direct",
-        "directory",
-        "discount",
-        "dnp",
-        "domains",
-        "durban",
-        "dvag",
-        "eat",
-        "edu",
-        "education",
-        "email",
-        "engineer",
-        "engineering",
-        "enterprises",
-        "equipment",
-        "esq",
-        "estate",
-        "eus",
-        "events",
-        "exchange",
-        "expert",
-        "exposed",
-        "fail",
-        "farm",
-        "feedback",
-        "finance",
-        "financial",
-        "fish",
-        "fishing",
-        "fitness",
-        "flights",
-        "florist",
-        "flsmidth",
-        "fly",
-        "foo",
-        "forsale",
-        "foundation",
-        "frl",
-        "frogans",
-        "fund",
-        "furniture",
-        "futbol",
-        "gal",
-        "gallery",
-        "gbiz",
-        "gent",
-        "gift",
-        "gifts",
-        "gives",
-        "glass",
-        "gle",
-        "global",
-        "globo",
-        "gmail",
-        "gmo",
-        "gmx",
-        "google",
-        "gop",
-        "gov",
-        "graphics",
-        "gratis",
-        "green",
-        "gripe",
-        "guide",
-        "guitars",
-        "guru",
-        "hamburg",
-        "haus",
-        "healthcare",
-        "help",
-        "here",
-        "hiphop",
-        "hiv",
-        "holdings",
-        "holiday",
-        "homes",
-        "horse",
-        "host",
-        "hosting",
-        "house",
-        "how",
-        "ibm",
-        "immo",
-        "immobilien",
-        "industries",
-        "info",
-        "ing",
-        "ink",
-        "institute",
-        "insure",
-        "int",
-        "international",
-        "investments",
-        "jetzt",
-        "jobs",
-        "joburg",
-        "juegos",
-        "kaufen",
-        "kim",
-        "kitchen",
-        "kiwi",
-        "koeln",
-        "krd",
-        "kred",
-        "lacaixa",
-        "land",
-        "lawyer",
-        "lease",
-        "lgbt",
-        "life",
-        "lighting",
-        "limited",
-        "limo",
-        "link",
-        "loans",
-        "london",
-        "lotto",
-        "ltda",
-        "luxe",
-        "luxury",
-        "maison",
-        "management",
-        "mango",
-        "market",
-        "marketing",
-        "media",
-        "meet",
-        "melbourne",
-        "meme",
-        "menu",
-        "miami",
-        "mil",
-        "mini",
-        "mobi",
-        "moda",
-        "moe",
-        "monash",
-        "mortgage",
-        "moscow",
-        "motorcycles",
-        "mov",
-        "museum",
-        "nagoya",
-        "name",
-        "navy",
-        "net",
-        "network",
-        "neustar",
-        "new",
-        "nexus",
-        "ngo",
-        "nhk",
-        "ninja",
-        "nra",
-        "nrw",
-        "nyc",
-        "okinawa",
-        "ong",
-        "onl",
-        "ooo",
-        "org",
-        "organic",
-        "otsuka",
-        "ovh",
-        "paris",
-        "partners",
-        "parts",
-        "pharmacy",
-        "photo",
-        "photography",
-        "photos",
-        "physio",
-        "pics",
-        "pictures",
-        "pink",
-        "pizza",
-        "place",
-        "plumbing",
-        "pohl",
-        "poker",
-        "post",
-        "praxi",
-        "press",
-        "pro",
-        "prod",
-        "productions",
-        "prof",
-        "properties",
-        "property",
-        "pub",
-        "qpon",
-        "quebec",
-        "realtor",
-        "recipes",
-        "red",
-        "rehab",
-        "reise",
-        "reisen",
-        "ren",
-        "rentals",
-        "repair",
-        "report",
-        "republican",
-        "rest",
-        "restaurant",
-        "reviews",
-        "rich",
-        "rio",
-        "rip",
-        "rocks",
-        "rodeo",
-        "rsvp",
-        "ruhr",
-        "ryukyu",
-        "saarland",
-        "sarl",
-        "sca",
-        "scb",
-        "schmidt",
-        "schule",
-        "scot",
-        "services",
-        "sexy",
-        "shiksha",
-        "shoes",
-        "singles",
-        "social",
-        "software",
-        "sohu",
-        "solar",
-        "solutions",
-        "soy",
-        "space",
-        "spiegel",
-        "supplies",
-        "supply",
-        "support",
-        "surf",
-        "surgery",
-        "suzuki",
-        "systems",
-        "tatar",
-        "tattoo",
-        "tax",
-        "technology",
-        "tel",
-        "tienda",
-        "tips",
-        "tirol",
-        "today",
-        "tokyo",
-        "tools",
-        "top",
-        "town",
-        "toys",
-        "trade",
-        "training",
-        "travel",
-        "tui",
-        "university",
-        "uno",
-        "uol",
-        "vacations",
-        "vegas",
-        "ventures",
-        "versicherung",
-        "vet",
-        "viajes",
-        "villas",
-        "vision",
-        "vlaanderen",
-        "vodka",
-        "vote",
-        "voting",
-        "voto",
-        "voyage",
-        "wales",
-        "wang",
-        "watch",
-        "webcam",
-        "website",
-        "wed",
-        "wedding",
-        "whoswho",
-        "wien",
-        "wiki",
-        "williamhill",
-        "wme",
-        "work",
-        "works",
-        "world",
-        "wtc",
-        "wtf",
-        "xxx",
-        "xyz",
-        "yachts",
-        "yandex",
-        "yoga",
-        "yokohama",
-        "youtube",
-        "zip",
-        "zone",
+         // Taken from Version 2016011900, Last Updated Tue Jan 19 07:07:02 2016 UTC
+        "aaa", // aaa American Automobile Association, Inc.
+        "aarp", // aarp AARP
+        "abb", // abb ABB Ltd
+        "abbott", // abbott Abbott Laboratories, Inc.
+        "abogado", // abogado Top Level Domain Holdings Limited
+        "academy", // academy Half Oaks, LLC
+        "accenture", // accenture Accenture plc
+        "accountant", // accountant dot Accountant Limited
+        "accountants", // accountants Knob Town, LLC
+        "aco", // aco ACO Severin Ahlmann GmbH &amp; Co. KG
+        "active", // active The Active Network, Inc
+        "actor", // actor United TLD Holdco Ltd.
+        "adac", // adac Allgemeiner Deutscher Automobil-Club e.V. (ADAC)
+        "ads", // ads Charleston Road Registry Inc.
+        "adult", // adult ICM Registry AD LLC
+        "aeg", // aeg Aktiebolaget Electrolux
+        "aero", // aero Societe Internationale de Telecommunications Aeronautique (SITA INC USA)
+        "afl", // afl Australian Football League
+        "agency", // agency Steel Falls, LLC
+        "aig", // aig American International Group, Inc.
+        "airforce", // airforce United TLD Holdco Ltd.
+        "airtel", // airtel Bharti Airtel Limited
+        "alibaba", // alibaba Alibaba Group Holding Limited
+        "alipay", // alipay Alibaba Group Holding Limited
+        "allfinanz", // allfinanz Allfinanz Deutsche Vermögensberatung Aktiengesellschaft
+        "alsace", // alsace REGION D ALSACE
+        "amica", // amica Amica Mutual Insurance Company
+        "amsterdam", // amsterdam Gemeente Amsterdam
+        "analytics", // analytics Campus IP LLC
+        "android", // android Charleston Road Registry Inc.
+        "apartments", // apartments June Maple, LLC
+        "app", // app Charleston Road Registry Inc.
+        "apple", // apple Apple Inc.
+        "aquarelle", // aquarelle Aquarelle.com
+        "aramco", // aramco Aramco Services Company
+        "archi", // archi STARTING DOT LIMITED
+        "army", // army United TLD Holdco Ltd.
+        "arte", // arte Association Relative à la Télévision Européenne G.E.I.E.
+        "asia", // asia DotAsia Organisation Ltd.
+        "associates", // associates Baxter Hill, LLC
+        "attorney", // attorney United TLD Holdco, Ltd
+        "auction", // auction United TLD HoldCo, Ltd.
+        "audi", // audi AUDI Aktiengesellschaft
+        "audio", // audio Uniregistry, Corp.
+        "author", // author Amazon Registry Services, Inc.
+        "auto", // auto Uniregistry, Corp.
+        "autos", // autos DERAutos, LLC
+        "axa", // axa AXA SA
+        "azure", // azure Microsoft Corporation
+        "baidu", // baidu Baidu, Inc.
+        "band", // band United TLD Holdco, Ltd
+        "bank", // bank fTLD Registry Services, LLC
+        "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+        "barcelona", // barcelona Municipi de Barcelona
+        "barclaycard", // barclaycard Barclays Bank PLC
+        "barclays", // barclays Barclays Bank PLC
+        "bargains", // bargains Half Hallow, LLC
+        "bauhaus", // bauhaus Werkhaus GmbH
+        "bayern", // bayern Bayern Connect GmbH
+        "bbc", // bbc British Broadcasting Corporation
+        "bbva", // bbva BANCO BILBAO VIZCAYA ARGENTARIA, S.A.
+        "bcn", // bcn Municipi de Barcelona
+        "beats", // beats Beats Electronics, LLC
+        "beer", // beer Top Level Domain Holdings Limited
+        "bentley", // bentley Bentley Motors Limited
+        "berlin", // berlin dotBERLIN GmbH &amp; Co. KG
+        "best", // best BestTLD Pty Ltd
+        "bet", // bet Afilias plc
+        "bharti", // bharti Bharti Enterprises (Holding) Private Limited
+        "bible", // bible American Bible Society
+        "bid", // bid dot Bid Limited
+        "bike", // bike Grand Hollow, LLC
+        "bing", // bing Microsoft Corporation
+        "bingo", // bingo Sand Cedar, LLC
+        "bio", // bio STARTING DOT LIMITED
+        "biz", // biz Neustar, Inc.
+        "black", // black Afilias Limited
+        "blackfriday", // blackfriday Uniregistry, Corp.
+        "bloomberg", // bloomberg Bloomberg IP Holdings LLC
+        "blue", // blue Afilias Limited
+        "bms", // bms Bristol-Myers Squibb Company
+        "bmw", // bmw Bayerische Motoren Werke Aktiengesellschaft
+        "bnl", // bnl Banca Nazionale del Lavoro
+        "bnpparibas", // bnpparibas BNP Paribas
+        "boats", // boats DERBoats, LLC
+        "boehringer", // boehringer Boehringer Ingelheim International GmbH
+        "bom", // bom Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+        "bond", // bond Bond University Limited
+        "boo", // boo Charleston Road Registry Inc.
+        "book", // book Amazon Registry Services, Inc.
+        "boots", // boots THE BOOTS COMPANY PLC
+        "bosch", // bosch Robert Bosch GMBH
+        "bostik", // bostik Bostik SA
+        "bot", // bot Amazon Registry Services, Inc.
+        "boutique", // boutique Over Galley, LLC
+        "bradesco", // bradesco Banco Bradesco S.A.
+        "bridgestone", // bridgestone Bridgestone Corporation
+        "broadway", // broadway Celebrate Broadway, Inc.
+        "broker", // broker DOTBROKER REGISTRY LTD
+        "brother", // brother Brother Industries, Ltd.
+        "brussels", // brussels DNS.be vzw
+        "budapest", // budapest Top Level Domain Holdings Limited
+        "bugatti", // bugatti Bugatti International SA
+        "build", // build Plan Bee LLC
+        "builders", // builders Atomic Madison, LLC
+        "business", // business Spring Cross, LLC
+        "buy", // buy Amazon Registry Services, INC
+        "buzz", // buzz DOTSTRATEGY CO.
+        "bzh", // bzh Association www.bzh
+        "cab", // cab Half Sunset, LLC
+        "cafe", // cafe Pioneer Canyon, LLC
+        "cal", // cal Charleston Road Registry Inc.
+        "call", // call Amazon Registry Services, Inc.
+        "camera", // camera Atomic Maple, LLC
+        "camp", // camp Delta Dynamite, LLC
+        "cancerresearch", // cancerresearch Australian Cancer Research Foundation
+        "canon", // canon Canon Inc.
+        "capetown", // capetown ZA Central Registry NPC trading as ZA Central Registry
+        "capital", // capital Delta Mill, LLC
+        "car", // car Cars Registry Limited
+        "caravan", // caravan Caravan International, Inc.
+        "cards", // cards Foggy Hollow, LLC
+        "care", // care Goose Cross, LLC
+        "career", // career dotCareer LLC
+        "careers", // careers Wild Corner, LLC
+        "cars", // cars Uniregistry, Corp.
+        "cartier", // cartier Richemont DNS Inc.
+        "casa", // casa Top Level Domain Holdings Limited
+        "cash", // cash Delta Lake, LLC
+        "casino", // casino Binky Sky, LLC
+        "cat", // cat Fundacio puntCAT
+        "catering", // catering New Falls. LLC
+        "cba", // cba COMMONWEALTH BANK OF AUSTRALIA
+        "cbn", // cbn The Christian Broadcasting Network, Inc.
+        "ceb", // ceb The Corporate Executive Board Company
+        "center", // center Tin Mill, LLC
+        "ceo", // ceo CEOTLD Pty Ltd
+        "cern", // cern European Organization for Nuclear Research (&quot;CERN&quot;)
+        "cfa", // cfa CFA Institute
+        "cfd", // cfd DOTCFD REGISTRY LTD
+        "chanel", // chanel Chanel International B.V.
+        "channel", // channel Charleston Road Registry Inc.
+        "chat", // chat Sand Fields, LLC
+        "cheap", // cheap Sand Cover, LLC
+        "chloe", // chloe Richemont DNS Inc.
+        "christmas", // christmas Uniregistry, Corp.
+        "chrome", // chrome Charleston Road Registry Inc.
+        "church", // church Holly Fileds, LLC
+        "cipriani", // cipriani Hotel Cipriani Srl
+        "circle", // circle Amazon Registry Services, Inc.
+        "cisco", // cisco Cisco Technology, Inc.
+        "citic", // citic CITIC Group Corporation
+        "city", // city Snow Sky, LLC
+        "cityeats", // cityeats Lifestyle Domain Holdings, Inc.
+        "claims", // claims Black Corner, LLC
+        "cleaning", // cleaning Fox Shadow, LLC
+        "click", // click Uniregistry, Corp.
+        "clinic", // clinic Goose Park, LLC
+        "clinique", // clinique The Estée Lauder Companies Inc.
+        "clothing", // clothing Steel Lake, LLC
+        "cloud", // cloud ARUBA S.p.A.
+        "club", // club .CLUB DOMAINS, LLC
+        "clubmed", // clubmed Club Méditerranée S.A.
+        "coach", // coach Koko Island, LLC
+        "codes", // codes Puff Willow, LLC
+        "coffee", // coffee Trixy Cover, LLC
+        "college", // college XYZ.COM LLC
+        "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH
+        "com", // com VeriSign Global Registry Services
+        "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA
+        "community", // community Fox Orchard, LLC
+        "company", // company Silver Avenue, LLC
+        "compare", // compare iSelect Ltd
+        "computer", // computer Pine Mill, LLC
+        "comsec", // comsec VeriSign, Inc.
+        "condos", // condos Pine House, LLC
+        "construction", // construction Fox Dynamite, LLC
+        "consulting", // consulting United TLD Holdco, LTD.
+        "contact", // contact Top Level Spectrum, Inc.
+        "contractors", // contractors Magic Woods, LLC
+        "cooking", // cooking Top Level Domain Holdings Limited
+        "cool", // cool Koko Lake, LLC
+        "coop", // coop DotCooperation LLC
+        "corsica", // corsica Collectivité Territoriale de Corse
+        "country", // country Top Level Domain Holdings Limited
+        "coupon", // coupon Amazon Registry Services, Inc.
+        "coupons", // coupons Black Island, LLC
+        "courses", // courses OPEN UNIVERSITIES AUSTRALIA PTY LTD
+        "credit", // credit Snow Shadow, LLC
+        "creditcard", // creditcard Binky Frostbite, LLC
+        "creditunion", // creditunion CUNA Performance Resources, LLC
+        "cricket", // cricket dot Cricket Limited
+        "crown", // crown Crown Equipment Corporation
+        "crs", // crs Federated Co-operatives Limited
+        "cruises", // cruises Spring Way, LLC
+        "csc", // csc Alliance-One Services, Inc.
+        "cuisinella", // cuisinella SALM S.A.S.
+        "cymru", // cymru Nominet UK
+        "cyou", // cyou Beijing Gamease Age Digital Technology Co., Ltd.
+        "dabur", // dabur Dabur India Limited
+        "dad", // dad Charleston Road Registry Inc.
+        "dance", // dance United TLD Holdco Ltd.
+        "date", // date dot Date Limited
+        "dating", // dating Pine Fest, LLC
+        "datsun", // datsun NISSAN MOTOR CO., LTD.
+        "day", // day Charleston Road Registry Inc.
+        "dclk", // dclk Charleston Road Registry Inc.
+        "dealer", // dealer Dealer Dot Com, Inc.
+        "deals", // deals Sand Sunset, LLC
+        "degree", // degree United TLD Holdco, Ltd
+        "delivery", // delivery Steel Station, LLC
+        "dell", // dell Dell Inc.
+        "deloitte", // deloitte Deloitte Touche Tohmatsu
+        "delta", // delta Delta Air Lines, Inc.
+        "democrat", // democrat United TLD Holdco Ltd.
+        "dental", // dental Tin Birch, LLC
+        "dentist", // dentist United TLD Holdco, Ltd
+        "desi", // desi Desi Networks LLC
+        "design", // design Top Level Design, LLC
+        "dev", // dev Charleston Road Registry Inc.
+        "diamonds", // diamonds John Edge, LLC
+        "diet", // diet Uniregistry, Corp.
+        "digital", // digital Dash Park, LLC
+        "direct", // direct Half Trail, LLC
+        "directory", // directory Extra Madison, LLC
+        "discount", // discount Holly Hill, LLC
+        "dnp", // dnp Dai Nippon Printing Co., Ltd.
+        "docs", // docs Charleston Road Registry Inc.
+        "dog", // dog Koko Mill, LLC
+        "doha", // doha Communications Regulatory Authority (CRA)
+        "domains", // domains Sugar Cross, LLC
+        "doosan", // doosan Doosan Corporation
+        "download", // download dot Support Limited
+        "drive", // drive Charleston Road Registry Inc.
+        "dubai", // dubai Dubai Smart Government Department
+        "durban", // durban ZA Central Registry NPC trading as ZA Central Registry
+        "dvag", // dvag Deutsche Vermögensberatung Aktiengesellschaft DVAG
+        "earth", // earth Interlink Co., Ltd.
+        "eat", // eat Charleston Road Registry Inc.
+        "edeka", // edeka EDEKA Verband kaufmännischer Genossenschaften e.V.
+        "edu", // edu EDUCAUSE
+        "education", // education Brice Way, LLC
+        "email", // email Spring Madison, LLC
+        "emerck", // emerck Merck KGaA
+        "energy", // energy Binky Birch, LLC
+        "engineer", // engineer United TLD Holdco Ltd.
+        "engineering", // engineering Romeo Canyon
+        "enterprises", // enterprises Snow Oaks, LLC
+        "epson", // epson Seiko Epson Corporation
+        "equipment", // equipment Corn Station, LLC
+        "erni", // erni ERNI Group Holding AG
+        "esq", // esq Charleston Road Registry Inc.
+        "estate", // estate Trixy Park, LLC
+        "eurovision", // eurovision European Broadcasting Union (EBU)
+        "eus", // eus Puntueus Fundazioa
+        "events", // events Pioneer Maple, LLC
+        "everbank", // everbank EverBank
+        "exchange", // exchange Spring Falls, LLC
+        "expert", // expert Magic Pass, LLC
+        "exposed", // exposed Victor Beach, LLC
+        "express", // express Sea Sunset, LLC
+        "fage", // fage Fage International S.A.
+        "fail", // fail Atomic Pipe, LLC
+        "fairwinds", // fairwinds FairWinds Partners, LLC
+        "faith", // faith dot Faith Limited
+        "family", // family United TLD Holdco Ltd.
+        "fan", // fan Asiamix Digital Ltd
+        "fans", // fans Asiamix Digital Limited
+        "farm", // farm Just Maple, LLC
+        "fashion", // fashion Top Level Domain Holdings Limited
+        "fast", // fast Amazon Registry Services, Inc.
+        "feedback", // feedback Top Level Spectrum, Inc.
+        "ferrero", // ferrero Ferrero Trading Lux S.A.
+        "film", // film Motion Picture Domain Registry Pty Ltd
+        "final", // final Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+        "finance", // finance Cotton Cypress, LLC
+        "financial", // financial Just Cover, LLC
+        "firestone", // firestone Bridgestone Corporation
+        "firmdale", // firmdale Firmdale Holdings Limited
+        "fish", // fish Fox Woods, LLC
+        "fishing", // fishing Top Level Domain Holdings Limited
+        "fit", // fit Minds + Machines Group Limited
+        "fitness", // fitness Brice Orchard, LLC
+        "flickr", // flickr Yahoo! Domain Services Inc.
+        "flights", // flights Fox Station, LLC
+        "florist", // florist Half Cypress, LLC
+        "flowers", // flowers Uniregistry, Corp.
+        "flsmidth", // flsmidth FLSmidth A/S
+        "fly", // fly Charleston Road Registry Inc.
+        "foo", // foo Charleston Road Registry Inc.
+        "football", // football Foggy Farms, LLC
+        "ford", // ford Ford Motor Company
+        "forex", // forex DOTFOREX REGISTRY LTD
+        "forsale", // forsale United TLD Holdco, LLC
+        "forum", // forum Fegistry, LLC
+        "foundation", // foundation John Dale, LLC
+        "fox", // fox FOX Registry, LLC
+        "fresenius", // fresenius Fresenius Immobilien-Verwaltungs-GmbH
+        "frl", // frl FRLregistry B.V.
+        "frogans", // frogans OP3FT
+        "frontier", // frontier Frontier Communications Corporation
+        "fund", // fund John Castle, LLC
+        "furniture", // furniture Lone Fields, LLC
+        "futbol", // futbol United TLD Holdco, Ltd.
+        "fyi", // fyi Silver Tigers, LLC
+        "gal", // gal Asociación puntoGAL
+        "gallery", // gallery Sugar House, LLC
+        "gallup", // gallup Gallup, Inc.
+        "game", // game Uniregistry, Corp.
+        "garden", // garden Top Level Domain Holdings Limited
+        "gbiz", // gbiz Charleston Road Registry Inc.
+        "gdn", // gdn Joint Stock Company "Navigation-information systems"
+        "gea", // gea GEA Group Aktiengesellschaft
+        "gent", // gent COMBELL GROUP NV/SA
+        "genting", // genting Resorts World Inc. Pte. Ltd.
+        "ggee", // ggee GMO Internet, Inc.
+        "gift", // gift Uniregistry, Corp.
+        "gifts", // gifts Goose Sky, LLC
+        "gives", // gives United TLD Holdco Ltd.
+        "giving", // giving Giving Limited
+        "glass", // glass Black Cover, LLC
+        "gle", // gle Charleston Road Registry Inc.
+        "global", // global Dot Global Domain Registry Limited
+        "globo", // globo Globo Comunicação e Participações S.A
+        "gmail", // gmail Charleston Road Registry Inc.
+        "gmo", // gmo GMO Internet, Inc.
+        "gmx", // gmx 1&amp;1 Mail &amp; Media GmbH
+        "gold", // gold June Edge, LLC
+        "goldpoint", // goldpoint YODOBASHI CAMERA CO.,LTD.
+        "golf", // golf Lone Falls, LLC
+        "goo", // goo NTT Resonant Inc.
+        "goog", // goog Charleston Road Registry Inc.
+        "google", // google Charleston Road Registry Inc.
+        "gop", // gop Republican State Leadership Committee, Inc.
+        "got", // got Amazon Registry Services, Inc.
+        "gov", // gov General Services Administration Attn: QTDC, 2E08 (.gov Domain Registration)
+        "grainger", // grainger Grainger Registry Services, LLC
+        "graphics", // graphics Over Madison, LLC
+        "gratis", // gratis Pioneer Tigers, LLC
+        "green", // green Afilias Limited
+        "gripe", // gripe Corn Sunset, LLC
+        "group", // group Romeo Town, LLC
+        "gucci", // gucci Guccio Gucci S.p.a.
+        "guge", // guge Charleston Road Registry Inc.
+        "guide", // guide Snow Moon, LLC
+        "guitars", // guitars Uniregistry, Corp.
+        "guru", // guru Pioneer Cypress, LLC
+        "hamburg", // hamburg Hamburg Top-Level-Domain GmbH
+        "hangout", // hangout Charleston Road Registry Inc.
+        "haus", // haus United TLD Holdco, LTD.
+        "hdfcbank", // hdfcbank HDFC Bank Limited
+        "health", // health DotHealth, LLC
+        "healthcare", // healthcare Silver Glen, LLC
+        "help", // help Uniregistry, Corp.
+        "helsinki", // helsinki City of Helsinki
+        "here", // here Charleston Road Registry Inc.
+        "hermes", // hermes Hermes International
+        "hiphop", // hiphop Uniregistry, Corp.
+        "hitachi", // hitachi Hitachi, Ltd.
+        "hiv", // hiv dotHIV gemeinnuetziger e.V.
+        "hockey", // hockey Half Willow, LLC
+        "holdings", // holdings John Madison, LLC
+        "holiday", // holiday Goose Woods, LLC
+        "homedepot", // homedepot Homer TLC, Inc.
+        "homes", // homes DERHomes, LLC
+        "honda", // honda Honda Motor Co., Ltd.
+        "horse", // horse Top Level Domain Holdings Limited
+        "host", // host DotHost Inc.
+        "hosting", // hosting Uniregistry, Corp.
+        "hoteles", // hoteles Travel Reservations SRL
+        "hotmail", // hotmail Microsoft Corporation
+        "house", // house Sugar Park, LLC
+        "how", // how Charleston Road Registry Inc.
+        "hsbc", // hsbc HSBC Holdings PLC
+        "hyundai", // hyundai Hyundai Motor Company
+        "ibm", // ibm International Business Machines Corporation
+        "icbc", // icbc Industrial and Commercial Bank of China Limited
+        "ice", // ice IntercontinentalExchange, Inc.
+        "icu", // icu One.com A/S
+        "ifm", // ifm ifm electronic gmbh
+        "iinet", // iinet Connect West Pty. Ltd.
+        "immo", // immo Auburn Bloom, LLC
+        "immobilien", // immobilien United TLD Holdco Ltd.
+        "industries", // industries Outer House, LLC
+        "infiniti", // infiniti NISSAN MOTOR CO., LTD.
+        "info", // info Afilias Limited
+        "ing", // ing Charleston Road Registry Inc.
+        "ink", // ink Top Level Design, LLC
+        "institute", // institute Outer Maple, LLC
+        "insurance", // insurance fTLD Registry Services LLC
+        "insure", // insure Pioneer Willow, LLC
+        "int", // int Internet Assigned Numbers Authority
+        "international", // international Wild Way, LLC
+        "investments", // investments Holly Glen, LLC
+        "ipiranga", // ipiranga Ipiranga Produtos de Petroleo S.A.
+        "irish", // irish Dot-Irish LLC
+        "iselect", // iselect iSelect Ltd
+        "ist", // ist Istanbul Metropolitan Municipality
+        "istanbul", // istanbul Istanbul Metropolitan Municipality / Medya A.S.
+        "itau", // itau Itau Unibanco Holding S.A.
+        "iwc", // iwc Richemont DNS Inc.
+        "jaguar", // jaguar Jaguar Land Rover Ltd
+        "java", // java Oracle Corporation
+        "jcb", // jcb JCB Co., Ltd.
+        "jetzt", // jetzt New TLD Company AB
+        "jewelry", // jewelry Wild Bloom, LLC
+        "jlc", // jlc Richemont DNS Inc.
+        "jll", // jll Jones Lang LaSalle Incorporated
+        "jmp", // jmp Matrix IP LLC
+        "jobs", // jobs Employ Media LLC
+        "joburg", // joburg ZA Central Registry NPC trading as ZA Central Registry
+        "jot", // jot Amazon Registry Services, Inc.
+        "joy", // joy Amazon Registry Services, Inc.
+        "jprs", // jprs Japan Registry Services Co., Ltd.
+        "juegos", // juegos Uniregistry, Corp.
+        "kaufen", // kaufen United TLD Holdco Ltd.
+        "kddi", // kddi KDDI CORPORATION
+        "kfh", // kfh Kuwait Finance House
+        "kia", // kia KIA MOTORS CORPORATION
+        "kim", // kim Afilias Limited
+        "kinder", // kinder Ferrero Trading Lux S.A.
+        "kitchen", // kitchen Just Goodbye, LLC
+        "kiwi", // kiwi DOT KIWI LIMITED
+        "koeln", // koeln NetCologne Gesellschaft für Telekommunikation mbH
+        "komatsu", // komatsu Komatsu Ltd.
+        "kpn", // kpn Koninklijke KPN N.V.
+        "krd", // krd KRG Department of Information Technology
+        "kred", // kred KredTLD Pty Ltd
+        "kyoto", // kyoto Academic Institution: Kyoto Jyoho Gakuen
+        "lacaixa", // lacaixa CAIXA D&#39;ESTALVIS I PENSIONS DE BARCELONA
+        "lamborghini", // lamborghini Automobili Lamborghini S.p.A.
+        "lamer", // lamer The Estée Lauder Companies Inc.
+        "lancaster", // lancaster LANCASTER
+        "land", // land Pine Moon, LLC
+        "landrover", // landrover Jaguar Land Rover Ltd
+        "lanxess", // lanxess LANXESS Corporation
+        "lasalle", // lasalle Jones Lang LaSalle Incorporated
+        "lat", // lat ECOM-LAC Federación de Latinoamérica y el Caribe para Internet y el Comercio Electrónico
+        "latrobe", // latrobe La Trobe University
+        "law", // law Minds + Machines Group Limited
+        "lawyer", // lawyer United TLD Holdco, Ltd
+        "lds", // lds IRI Domain Management, LLC
+        "lease", // lease Victor Trail, LLC
+        "leclerc", // leclerc A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc
+        "legal", // legal Blue Falls, LLC
+        "lexus", // lexus TOYOTA MOTOR CORPORATION
+        "lgbt", // lgbt Afilias Limited
+        "liaison", // liaison Liaison Technologies, Incorporated
+        "lidl", // lidl Schwarz Domains und Services GmbH &amp; Co. KG
+        "life", // life Trixy Oaks, LLC
+        "lifeinsurance", // lifeinsurance American Council of Life Insurers
+        "lifestyle", // lifestyle Lifestyle Domain Holdings, Inc.
+        "lighting", // lighting John McCook, LLC
+        "like", // like Amazon Registry Services, Inc.
+        "limited", // limited Big Fest, LLC
+        "limo", // limo Hidden Frostbite, LLC
+        "lincoln", // lincoln Ford Motor Company
+        "linde", // linde Linde Aktiengesellschaft
+        "link", // link Uniregistry, Corp.
+        "live", // live United TLD Holdco Ltd.
+        "living", // living Lifestyle Domain Holdings, Inc.
+        "lixil", // lixil LIXIL Group Corporation
+        "loan", // loan dot Loan Limited
+        "loans", // loans June Woods, LLC
+        "lol", // lol Uniregistry, Corp.
+        "london", // london Dot London Domains Limited
+        "lotte", // lotte Lotte Holdings Co., Ltd.
+        "lotto", // lotto Afilias Limited
+        "love", // love Merchant Law Group LLP
+        "ltd", // ltd Over Corner, LLC
+        "ltda", // ltda InterNetX Corp.
+        "lupin", // lupin LUPIN LIMITED
+        "luxe", // luxe Top Level Domain Holdings Limited
+        "luxury", // luxury Luxury Partners LLC
+        "madrid", // madrid Comunidad de Madrid
+        "maif", // maif Mutuelle Assurance Instituteur France (MAIF)
+        "maison", // maison Victor Frostbite, LLC
+        "makeup", // makeup L&#39;Oréal
+        "man", // man MAN SE
+        "management", // management John Goodbye, LLC
+        "mango", // mango PUNTO FA S.L.
+        "market", // market Unitied TLD Holdco, Ltd
+        "marketing", // marketing Fern Pass, LLC
+        "markets", // markets DOTMARKETS REGISTRY LTD
+        "marriott", // marriott Marriott Worldwide Corporation
+        "mba", // mba Lone Hollow, LLC
+        "med", // med Medistry LLC
+        "media", // media Grand Glen, LLC
+        "meet", // meet Afilias Limited
+        "melbourne", // melbourne The Crown in right of the State of Victoria
+        "meme", // meme Charleston Road Registry Inc.
+        "memorial", // memorial Dog Beach, LLC
+        "men", // men Exclusive Registry Limited
+        "menu", // menu Wedding TLD2, LLC
+        "meo", // meo PT Comunicacoes S.A.
+        "miami", // miami Top Level Domain Holdings Limited
+        "microsoft", // microsoft Microsoft Corporation
+        "mil", // mil DoD Network Information Center
+        "mini", // mini Bayerische Motoren Werke Aktiengesellschaft
+        "mma", // mma MMA IARD
+        "mobi", // mobi Afilias Technologies Limited dba dotMobi
+        "mobily", // mobily GreenTech Consultancy Company W.L.L.
+        "moda", // moda United TLD Holdco Ltd.
+        "moe", // moe Interlink Co., Ltd.
+        "moi", // moi Amazon Registry Services, Inc.
+        "mom", // mom Uniregistry, Corp.
+        "monash", // monash Monash University
+        "money", // money Outer McCook, LLC
+        "montblanc", // montblanc Richemont DNS Inc.
+        "mormon", // mormon IRI Domain Management, LLC (&quot;Applicant&quot;)
+        "mortgage", // mortgage United TLD Holdco, Ltd
+        "moscow", // moscow Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+        "motorcycles", // motorcycles DERMotorcycles, LLC
+        "mov", // mov Charleston Road Registry Inc.
+        "movie", // movie New Frostbite, LLC
+        "movistar", // movistar Telefónica S.A.
+        "mtn", // mtn MTN Dubai Limited
+        "mtpc", // mtpc Mitsubishi Tanabe Pharma Corporation
+        "mtr", // mtr MTR Corporation Limited
+        "museum", // museum Museum Domain Management Association
+        "mutuelle", // mutuelle Fédération Nationale de la Mutualité Française
+        "nadex", // nadex Nadex Domains, Inc
+        "nagoya", // nagoya GMO Registry, Inc.
+        "name", // name VeriSign Information Services, Inc.
+        "natura", // natura NATURA COSMÉTICOS S.A.
+        "navy", // navy United TLD Holdco Ltd.
+        "nec", // nec NEC Corporation
+        "net", // net VeriSign Global Registry Services
+        "netbank", // netbank COMMONWEALTH BANK OF AUSTRALIA
+        "network", // network Trixy Manor, LLC
+        "neustar", // neustar NeuStar, Inc.
+        "new", // new Charleston Road Registry Inc.
+        "news", // news United TLD Holdco Ltd.
+        "nexus", // nexus Charleston Road Registry Inc.
+        "ngo", // ngo Public Interest Registry
+        "nhk", // nhk Japan Broadcasting Corporation (NHK)
+        "nico", // nico DWANGO Co., Ltd.
+        "nikon", // nikon NIKON CORPORATION
+        "ninja", // ninja United TLD Holdco Ltd.
+        "nissan", // nissan NISSAN MOTOR CO., LTD.
+        "nokia", // nokia Nokia Corporation
+        "norton", // norton Symantec Corporation
+        "nowruz", // nowruz Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+        "nra", // nra NRA Holdings Company, INC.
+        "nrw", // nrw Minds + Machines GmbH
+        "ntt", // ntt NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+        "nyc", // nyc The City of New York by and through the New York City Department of Information Technology &amp; Telecommunications
+        "obi", // obi OBI Group Holding SE &amp; Co. KGaA
+        "office", // office Microsoft Corporation
+        "okinawa", // okinawa BusinessRalliart inc.
+        "omega", // omega The Swatch Group Ltd
+        "one", // one One.com A/S
+        "ong", // ong Public Interest Registry
+        "onl", // onl I-REGISTRY Ltd., Niederlassung Deutschland
+        "online", // online DotOnline Inc.
+        "ooo", // ooo INFIBEAM INCORPORATION LIMITED
+        "oracle", // oracle Oracle Corporation
+        "orange", // orange Orange Brand Services Limited
+        "org", // org Public Interest Registry (PIR)
+        "organic", // organic Afilias Limited
+        "origins", // origins The Estée Lauder Companies Inc.
+        "osaka", // osaka Interlink Co., Ltd.
+        "otsuka", // otsuka Otsuka Holdings Co., Ltd.
+        "ovh", // ovh OVH SAS
+        "page", // page Charleston Road Registry Inc.
+        "pamperedchef", // pamperedchef The Pampered Chef, Ltd.
+        "panerai", // panerai Richemont DNS Inc.
+        "paris", // paris City of Paris
+        "pars", // pars Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+        "partners", // partners Magic Glen, LLC
+        "parts", // parts Sea Goodbye, LLC
+        "party", // party Blue Sky Registry Limited
+        "pet", // pet Afilias plc
+        "pharmacy", // pharmacy National Association of Boards of Pharmacy
+        "philips", // philips Koninklijke Philips N.V.
+        "photo", // photo Uniregistry, Corp.
+        "photography", // photography Sugar Glen, LLC
+        "photos", // photos Sea Corner, LLC
+        "physio", // physio PhysBiz Pty Ltd
+        "piaget", // piaget Richemont DNS Inc.
+        "pics", // pics Uniregistry, Corp.
+        "pictet", // pictet Pictet Europe S.A.
+        "pictures", // pictures Foggy Sky, LLC
+        "pid", // pid Top Level Spectrum, Inc.
+        "pin", // pin Amazon Registry Services, Inc.
+        "ping", // ping Ping Registry Provider, Inc.
+        "pink", // pink Afilias Limited
+        "pizza", // pizza Foggy Moon, LLC
+        "place", // place Snow Galley, LLC
+        "play", // play Charleston Road Registry Inc.
+        "playstation", // playstation Sony Computer Entertainment Inc.
+        "plumbing", // plumbing Spring Tigers, LLC
+        "plus", // plus Sugar Mill, LLC
+        "pohl", // pohl Deutsche Vermögensberatung Aktiengesellschaft DVAG
+        "poker", // poker Afilias Domains No. 5 Limited
+        "porn", // porn ICM Registry PN LLC
+        "post", // post Universal Postal Union
+        "praxi", // praxi Praxi S.p.A.
+        "press", // press DotPress Inc.
+        "pro", // pro Registry Services Corporation dba RegistryPro
+        "prod", // prod Charleston Road Registry Inc.
+        "productions", // productions Magic Birch, LLC
+        "prof", // prof Charleston Road Registry Inc.
+        "promo", // promo Afilias plc
+        "properties", // properties Big Pass, LLC
+        "property", // property Uniregistry, Corp.
+        "protection", // protection XYZ.COM LLC
+        "pub", // pub United TLD Holdco Ltd.
+        "pwc", // pwc PricewaterhouseCoopers LLP
+        "qpon", // qpon dotCOOL, Inc.
+        "quebec", // quebec PointQuébec Inc
+        "quest", // quest Quest ION Limited
+        "racing", // racing Premier Registry Limited
+        "read", // read Amazon Registry Services, Inc.
+        "realtor", // realtor Real Estate Domains LLC
+        "realty", // realty Fegistry, LLC
+        "recipes", // recipes Grand Island, LLC
+        "red", // red Afilias Limited
+        "redstone", // redstone Redstone Haute Couture Co., Ltd.
+        "redumbrella", // redumbrella Travelers TLD, LLC
+        "rehab", // rehab United TLD Holdco Ltd.
+        "reise", // reise Foggy Way, LLC
+        "reisen", // reisen New Cypress, LLC
+        "reit", // reit National Association of Real Estate Investment Trusts, Inc.
+        "ren", // ren Beijing Qianxiang Wangjing Technology Development Co., Ltd.
+        "rent", // rent XYZ.COM LLC
+        "rentals", // rentals Big Hollow,LLC
+        "repair", // repair Lone Sunset, LLC
+        "report", // report Binky Glen, LLC
+        "republican", // republican United TLD Holdco Ltd.
+        "rest", // rest Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+        "restaurant", // restaurant Snow Avenue, LLC
+        "review", // review dot Review Limited
+        "reviews", // reviews United TLD Holdco, Ltd.
+        "rexroth", // rexroth Robert Bosch GMBH
+        "rich", // rich I-REGISTRY Ltd., Niederlassung Deutschland
+        "ricoh", // ricoh Ricoh Company, Ltd.
+        "rio", // rio Empresa Municipal de Informática SA - IPLANRIO
+        "rip", // rip United TLD Holdco Ltd.
+        "rocher", // rocher Ferrero Trading Lux S.A.
+        "rocks", // rocks United TLD Holdco, LTD.
+        "rodeo", // rodeo Top Level Domain Holdings Limited
+        "room", // room Amazon Registry Services, Inc.
+        "rsvp", // rsvp Charleston Road Registry Inc.
+        "ruhr", // ruhr regiodot GmbH &amp; Co. KG
+        "run", // run Snow Park, LLC
+        "rwe", // rwe RWE AG
+        "ryukyu", // ryukyu BusinessRalliart inc.
+        "saarland", // saarland dotSaarland GmbH
+        "safe", // safe Amazon Registry Services, Inc.
+        "safety", // safety Safety Registry Services, LLC.
+        "sakura", // sakura SAKURA Internet Inc.
+        "sale", // sale United TLD Holdco, Ltd
+        "salon", // salon Outer Orchard, LLC
+        "samsung", // samsung SAMSUNG SDS CO., LTD
+        "sandvik", // sandvik Sandvik AB
+        "sandvikcoromant", // sandvikcoromant Sandvik AB
+        "sanofi", // sanofi Sanofi
+        "sap", // sap SAP AG
+        "sapo", // sapo PT Comunicacoes S.A.
+        "sarl", // sarl Delta Orchard, LLC
+        "sas", // sas Research IP LLC
+        "saxo", // saxo Saxo Bank A/S
+        "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION
+        "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ)
+        "scb", // scb The Siam Commercial Bank Public Company Limited (&quot;SCB&quot;)
+        "schaeffler", // schaeffler Schaeffler Technologies AG &amp; Co. KG
+        "schmidt", // schmidt SALM S.A.S.
+        "scholarships", // scholarships Scholarships.com, LLC
+        "school", // school Little Galley, LLC
+        "schule", // schule Outer Moon, LLC
+        "schwarz", // schwarz Schwarz Domains und Services GmbH &amp; Co. KG
+        "science", // science dot Science Limited
+        "scor", // scor SCOR SE
+        "scot", // scot Dot Scot Registry Limited
+        "seat", // seat SEAT, S.A. (Sociedad Unipersonal)
+        "security", // security XYZ.COM LLC
+        "seek", // seek Seek Limited
+        "select", // select iSelect Ltd
+        "sener", // sener Sener Ingeniería y Sistemas, S.A.
+        "services", // services Fox Castle, LLC
+        "seven", // seven Seven West Media Ltd
+        "sew", // sew SEW-EURODRIVE GmbH &amp; Co KG
+        "sex", // sex ICM Registry SX LLC
+        "sexy", // sexy Uniregistry, Corp.
+        "sfr", // sfr Societe Francaise du Radiotelephone - SFR
+        "sharp", // sharp Sharp Corporation
+        "shell", // shell Shell Information Technology International Inc
+        "shia", // shia Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+        "shiksha", // shiksha Afilias Limited
+        "shoes", // shoes Binky Galley, LLC
+        "show", // show Snow Beach, LLC
+        "shriram", // shriram Shriram Capital Ltd.
+        "singles", // singles Fern Madison, LLC
+        "site", // site DotSite Inc.
+        "ski", // ski STARTING DOT LIMITED
+        "skin", // skin L&#39;Oréal
+        "sky", // sky Sky International AG
+        "skype", // skype Microsoft Corporation
+        "smile", // smile Amazon Registry Services, Inc.
+        "sncf", // sncf SNCF (Société Nationale des Chemins de fer Francais)
+        "soccer", // soccer Foggy Shadow, LLC
+        "social", // social United TLD Holdco Ltd.
+        "softbank", // softbank SoftBank Group Corp.
+        "software", // software United TLD Holdco, Ltd
+        "sohu", // sohu Sohu.com Limited
+        "solar", // solar Ruby Town, LLC
+        "solutions", // solutions Silver Cover, LLC
+        "sony", // sony Sony Corporation
+        "soy", // soy Charleston Road Registry Inc.
+        "space", // space DotSpace Inc.
+        "spiegel", // spiegel SPIEGEL-Verlag Rudolf Augstein GmbH &amp; Co. KG
+        "spot", // spot Amazon Registry Services, Inc.
+        "spreadbetting", // spreadbetting DOTSPREADBETTING REGISTRY LTD
+        "srl", // srl InterNetX Corp.
+        "stada", // stada STADA Arzneimittel AG
+        "star", // star Star India Private Limited
+        "starhub", // starhub StarHub Limited
+        "statefarm", // statefarm State Farm Mutual Automobile Insurance Company
+        "statoil", // statoil Statoil ASA
+        "stc", // stc Saudi Telecom Company
+        "stcgroup", // stcgroup Saudi Telecom Company
+        "stockholm", // stockholm Stockholms kommun
+        "storage", // storage Self Storage Company LLC
+        "studio", // studio United TLD Holdco Ltd.
+        "study", // study OPEN UNIVERSITIES AUSTRALIA PTY LTD
+        "style", // style Binky Moon, LLC
+        "sucks", // sucks Vox Populi Registry Ltd.
+        "supplies", // supplies Atomic Fields, LLC
+        "supply", // supply Half Falls, LLC
+        "support", // support Grand Orchard, LLC
+        "surf", // surf Top Level Domain Holdings Limited
+        "surgery", // surgery Tin Avenue, LLC
+        "suzuki", // suzuki SUZUKI MOTOR CORPORATION
+        "swatch", // swatch The Swatch Group Ltd
+        "swiss", // swiss Swiss Confederation
+        "sydney", // sydney State of New South Wales, Department of Premier and Cabinet
+        "symantec", // symantec Symantec Corporation
+        "systems", // systems Dash Cypress, LLC
+        "tab", // tab Tabcorp Holdings Limited
+        "taipei", // taipei Taipei City Government
+        "taobao", // taobao Alibaba Group Holding Limited
+        "tatamotors", // tatamotors Tata Motors Ltd
+        "tatar", // tatar Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic"
+        "tattoo", // tattoo Uniregistry, Corp.
+        "tax", // tax Storm Orchard, LLC
+        "taxi", // taxi Pine Falls, LLC
+        "tci", // tci Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+        "team", // team Atomic Lake, LLC
+        "tech", // tech Dot Tech LLC
+        "technology", // technology Auburn Falls, LLC
+        "tel", // tel Telnic Ltd.
+        "telefonica", // telefonica Telefónica S.A.
+        "temasek", // temasek Temasek Holdings (Private) Limited
+        "tennis", // tennis Cotton Bloom, LLC
+        "thd", // thd Homer TLC, Inc.
+        "theater", // theater Blue Tigers, LLC
+        "theatre", // theatre XYZ.COM LLC
+        "tickets", // tickets Accent Media Limited
+        "tienda", // tienda Victor Manor, LLC
+        "tiffany", // tiffany Tiffany and Company
+        "tips", // tips Corn Willow, LLC
+        "tires", // tires Dog Edge, LLC
+        "tirol", // tirol punkt Tirol GmbH
+        "tmall", // tmall Alibaba Group Holding Limited
+        "today", // today Pearl Woods, LLC
+        "tokyo", // tokyo GMO Registry, Inc.
+        "tools", // tools Pioneer North, LLC
+        "top", // top Jiangsu Bangning Science &amp; Technology Co.,Ltd.
+        "toray", // toray Toray Industries, Inc.
+        "toshiba", // toshiba TOSHIBA Corporation
+        "tours", // tours Sugar Station, LLC
+        "town", // town Koko Moon, LLC
+        "toyota", // toyota TOYOTA MOTOR CORPORATION
+        "toys", // toys Pioneer Orchard, LLC
+        "trade", // trade Elite Registry Limited
+        "trading", // trading DOTTRADING REGISTRY LTD
+        "training", // training Wild Willow, LLC
+        "travel", // travel Tralliance Registry Management Company, LLC.
+        "travelers", // travelers Travelers TLD, LLC
+        "travelersinsurance", // travelersinsurance Travelers TLD, LLC
+        "trust", // trust Artemis Internet Inc
+        "trv", // trv Travelers TLD, LLC
+        "tube", // tube Latin American Telecom LLC
+        "tui", // tui TUI AG
+        "tushu", // tushu Amazon Registry Services, Inc.
+        "tvs", // tvs T V SUNDRAM IYENGAR  &amp; SONS PRIVATE LIMITED
+        "ubs", // ubs UBS AG
+        "unicom", // unicom China United Network Communications Corporation Limited
+        "university", // university Little Station, LLC
+        "uno", // uno Dot Latin LLC
+        "uol", // uol UBN INTERNET LTDA.
+        "vacations", // vacations Atomic Tigers, LLC
+        "vana", // vana Lifestyle Domain Holdings, Inc.
+        "vegas", // vegas Dot Vegas, Inc.
+        "ventures", // ventures Binky Lake, LLC
+        "verisign", // verisign VeriSign, Inc.
+        "versicherung", // versicherung dotversicherung-registry GmbH
+        "vet", // vet United TLD Holdco, Ltd
+        "viajes", // viajes Black Madison, LLC
+        "video", // video United TLD Holdco, Ltd
+        "villas", // villas New Sky, LLC
+        "vin", // vin Holly Shadow, LLC
+        "vip", // vip Minds + Machines Group Limited
+        "virgin", // virgin Virgin Enterprises Limited
+        "vision", // vision Koko Station, LLC
+        "vista", // vista Vistaprint Limited
+        "vistaprint", // vistaprint Vistaprint Limited
+        "viva", // viva Saudi Telecom Company
+        "vlaanderen", // vlaanderen DNS.be vzw
+        "vodka", // vodka Top Level Domain Holdings Limited
+        "volkswagen", // volkswagen Volkswagen Group of America Inc.
+        "vote", // vote Monolith Registry LLC
+        "voting", // voting Valuetainment Corp.
+        "voto", // voto Monolith Registry LLC
+        "voyage", // voyage Ruby House, LLC
+        "wales", // wales Nominet UK
+        "walter", // walter Sandvik AB
+        "wang", // wang Zodiac Registry Limited
+        "wanggou", // wanggou Amazon Registry Services, Inc.
+        "watch", // watch Sand Shadow, LLC
+        "watches", // watches Richemont DNS Inc.
+        "weather", // weather The Weather Channel, LLC
+        "weatherchannel", // weatherchannel The Weather Channel, LLC
+        "webcam", // webcam dot Webcam Limited
+        "weber", // weber Saint-Gobain Weber SA
+        "website", // website DotWebsite Inc.
+        "wed", // wed Atgron, Inc.
+        "wedding", // wedding Top Level Domain Holdings Limited
+        "weir", // weir Weir Group IP Limited
+        "whoswho", // whoswho Who&#39;s Who Registry
+        "wien", // wien punkt.wien GmbH
+        "wiki", // wiki Top Level Design, LLC
+        "williamhill", // williamhill William Hill Organization Limited
+        "win", // win First Registry Limited
+        "windows", // windows Microsoft Corporation
+        "wine", // wine June Station, LLC
+        "wme", // wme William Morris Endeavor Entertainment, LLC
+        "wolterskluwer", // wolterskluwer Wolters Kluwer N.V.
+        "work", // work Top Level Domain Holdings Limited
+        "works", // works Little Dynamite, LLC
+        "world", // world Bitter Fields, LLC
+        "wtc", // wtc World Trade Centers Association, Inc.
+        "wtf", // wtf Hidden Way, LLC
+        "xbox", // xbox Microsoft Corporation
+        "xerox", // xerox Xerox DNHC LLC
+        "xin", // xin Elegant Leader Limited
+        "xn--11b4c3d", // कॉम VeriSign Sarl
+        "xn--1ck2e1b", // セール Amazon Registry Services, Inc.
+        "xn--1qqw23a", // 佛山 Guangzhou YU Wei Information Technology Co., Ltd.
+        "xn--30rr7y", // 慈善 Excellent First Limited
+        "xn--3bst00m", // 集团 Eagle Horizon Limited
+        "xn--3ds443g", // 在线 TLD REGISTRY LIMITED
+        "xn--3pxu8k", // 点看 VeriSign Sarl
+        "xn--42c2d9a", // คอม VeriSign Sarl
+        "xn--45q11c", // 八卦 Zodiac Scorpio Limited
+        "xn--4gbrim", // موقع Suhub Electronic Establishment
+        "xn--55qw42g", // 公益 China Organizational Name Administration Center
+        "xn--55qx5d", // 公司 Computer Network Information Center of Chinese Academy of Sciences （China Internet Network Information Center）
+        "xn--6frz82g", // 移动 Afilias Limited
+        "xn--6qq986b3xl", // 我爱你 Tycoon Treasure Limited
+        "xn--80adxhks", // москва Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+        "xn--80asehdb", // онлайн CORE Association
+        "xn--80aswg", // сайт CORE Association
+        "xn--8y0a063a", // 联通 China United Network Communications Corporation Limited
+        "xn--9dbq2a", // קום VeriSign Sarl
+        "xn--9et52u", // 时尚 RISE VICTORY LIMITED
+        "xn--b4w605ferd", // 淡马锡 Temasek Holdings (Private) Limited
+        "xn--bck1b9a5dre4c", // ファッション Amazon Registry Services, Inc.
+        "xn--c1avg", // орг Public Interest Registry
+        "xn--c2br7g", // नेट VeriSign Sarl
+        "xn--cck2b3b", // ストア Amazon Registry Services, Inc.
+        "xn--cg4bki", // 삼성 SAMSUNG SDS CO., LTD
+        "xn--czr694b", // 商标 HU YI GLOBAL INFORMATION RESOURCES(HOLDING) COMPANY.HONGKONG LIMITED
+        "xn--czrs0t", // 商店 Wild Island, LLC
+        "xn--czru2d", // 商城 Zodiac Aquarius Limited
+        "xn--d1acj3b", // дети The Foundation for Network Initiatives “The Smart Internet”
+        "xn--eckvdtc9d", // ポイント Amazon Registry Services, Inc.
+        "xn--efvy88h", // 新闻 Xinhua News Agency Guangdong Branch 新华通讯社广东分社
+        "xn--estv75g", // 工行 Industrial and Commercial Bank of China Limited
+        "xn--fhbei", // كوم VeriSign Sarl
+        "xn--fiq228c5hs", // 中文网 TLD REGISTRY LIMITED
+        "xn--fiq64b", // 中信 CITIC Group Corporation
+        "xn--fjq720a", // 娱乐 Will Bloom, LLC
+        "xn--flw351e", // 谷歌 Charleston Road Registry Inc.
+        "xn--g2xx48c", // 购物 Minds + Machines Group Limited
+        "xn--gckr3f0f", // クラウド Amazon Registry Services, Inc.
+        "xn--hxt814e", // 网店 Zodiac Libra Limited
+        "xn--i1b6b1a6a2e", // संगठन Public Interest Registry
+        "xn--imr513n", // 餐厅 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED
+        "xn--io0a7i", // 网络 Computer Network Information Center of Chinese Academy of Sciences （China Internet Network Information Center）
+        "xn--j1aef", // ком VeriSign Sarl
+        "xn--jlq61u9w7b", // 诺基亚 Nokia Corporation
+        "xn--kcrx77d1x4a", // 飞利浦 Koninklijke Philips N.V.
+        "xn--kpu716f", // 手表 Richemont DNS Inc.
+        "xn--kput3i", // 手机 Beijing RITT-Net Technology Development Co., Ltd
+        "xn--mgba3a3ejt", // ارامكو Aramco Services Company
+        "xn--mgbab2bd", // بازار CORE Association
+        "xn--mgbb9fbpob", // موبايلي GreenTech Consultancy Company W.L.L.
+        "xn--mgbt3dhd", // همراه Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+        "xn--mk1bu44c", // 닷컴 VeriSign Sarl
+        "xn--mxtq1m", // 政府 Net-Chinese Co., Ltd.
+        "xn--ngbc5azd", // شبكة International Domain Registry Pty. Ltd.
+        "xn--ngbe9e0a", // بيتك Kuwait Finance House
+        "xn--nqv7f", // 机构 Public Interest Registry
+        "xn--nqv7fs00ema", // 组织机构 Public Interest Registry
+        "xn--nyqy26a", // 健康 Stable Tone Limited
+        "xn--p1acf", // рус Rusnames Limited
+        "xn--pbt977c", // 珠宝 Richemont DNS Inc.
+        "xn--pssy2u", // 大拿 VeriSign Sarl
+        "xn--q9jyb4c", // みんな Charleston Road Registry Inc.
+        "xn--qcka1pmc", // グーグル Charleston Road Registry Inc.
+        "xn--rhqv96g", // 世界 Stable Tone Limited
+        "xn--rovu88b", // 書籍 Amazon EU S.à r.l.
+        "xn--ses554g", // 网址 KNET Co., Ltd
+        "xn--t60b56a", // 닷넷 VeriSign Sarl
+        "xn--tckwe", // コム VeriSign Sarl
+        "xn--unup4y", // 游戏 Spring Fields, LLC
+        "xn--vermgensberater-ctb", // VERMöGENSBERATER Deutsche Vermögensberatung Aktiengesellschaft DVAG
+        "xn--vermgensberatung-pwb", // VERMöGENSBERATUNG Deutsche Vermögensberatung Aktiengesellschaft DVAG
+        "xn--vhquv", // 企业 Dash McCook, LLC
+        "xn--vuq861b", // 信息 Beijing Tele-info Network Technology Co., Ltd.
+        "xn--xhq521b", // 广东 Guangzhou YU Wei Information Technology Co., Ltd.
+        "xn--zfr164b", // 政务 China Organizational Name Administration Center
+        "xperia", // xperia Sony Mobile Communications AB
+        "xxx", // xxx ICM Registry LLC
+        "xyz", // xyz XYZ.COM LLC
+        "yachts", // yachts DERYachts, LLC
+        "yahoo", // yahoo Yahoo! Domain Services Inc.
+        "yamaxun", // yamaxun Amazon Registry Services, Inc.
+        "yandex", // yandex YANDEX, LLC
+        "yodobashi", // yodobashi YODOBASHI CAMERA CO.,LTD.
+        "yoga", // yoga Top Level Domain Holdings Limited
+        "yokohama", // yokohama GMO Registry, Inc.
+        "youtube", // youtube Charleston Road Registry Inc.
+        "zara", // zara Industria de Diseño Textil, S.A. (INDITEX, S.A.)
+        "zero", // zero Amazon Registry Services, Inc.
+        "zip", // zip Charleston Road Registry Inc.
+        "zone", // zone Outer Falls, LLC
+        "zuerich", // zuerich Kanton Zürich (Canton of Zurich)
     };
 
-    // JOSM PATCH BEGIN
-    // see #10862 - list of IDN TLDs taken from IANA on 2014-12-18
-    private static final String[] IDN_TLDS = new String[] {
-        "XN--1QQW23A",
-        "XN--3BST00M",
-        "XN--3DS443G",
-        "XN--3E0B707E",
-        "XN--45BRJ9C",
-        "XN--45Q11C",
-        "XN--4GBRIM",
-        "XN--55QW42G",
-        "XN--55QX5D",
-        "XN--6FRZ82G",
-        "XN--6QQ986B3XL",
-        "XN--80ADXHKS",
-        "XN--80AO21A",
-        "XN--80ASEHDB",
-        "XN--80ASWG",
-        "XN--90A3AC",
-        "XN--C1AVG",
-        "XN--CG4BKI",
-        "XN--CLCHC0EA0B2G2A9GCD",
-        "XN--CZR694B",
-        "XN--CZRS0T",
-        "XN--CZRU2D",
-        "XN--D1ACJ3B",
-        "XN--D1ALF",
-        "XN--FIQ228C5HS",
-        "XN--FIQ64B",
-        "XN--FIQS8S",
-        "XN--FIQZ9S",
-        "XN--FLW351E",
-        "XN--FPCRJ9C3D",
-        "XN--FZC2C9E2C",
-        "XN--GECRJ9C",
-        "XN--H2BRJ9C",
-        "XN--HXT814E",
-        "XN--I1B6B1A6A2E",
-        "XN--IO0A7I",
-        "XN--J1AMH",
-        "XN--J6W193G",
-        "XN--KPRW13D",
-        "XN--KPRY57D",
-        "XN--KPUT3I",
-        "XN--L1ACC",
-        "XN--LGBBAT1AD8J",
-        "XN--MGB9AWBF",
-        "XN--MGBA3A4F16A",
-        "XN--MGBAAM7A8H",
-        "XN--MGBAB2BD",
-        "XN--MGBAYH7GPA",
-        "XN--MGBBH1A71E",
-        "XN--MGBC0A9AZCG",
-        "XN--MGBERP4A5D4AR",
-        "XN--MGBX4CD0AB",
-        "XN--NGBC5AZD",
-        "XN--NODE",
-        "XN--NQV7F",
-        "XN--NQV7FS00EMA",
-        "XN--O3CW4H",
-        "XN--OGBPF8FL",
-        "XN--P1ACF",
-        "XN--P1AI",
-        "XN--PGBS0DH",
-        "XN--Q9JYB4C",
-        "XN--QCKA1PMC",
-        "XN--RHQV96G",
-        "XN--S9BRJ9C",
-        "XN--SES554G",
-        "XN--UNUP4Y",
-        "XN--VERMGENSBERATER-CTB",
-        "XN--VERMGENSBERATUNG-PWB",
-        "XN--VHQUV",
-        "XN--WGBH1C",
-        "XN--WGBL6A",
-        "XN--XHQ521B",
-        "XN--XKC2AL3HYE2A",
-        "XN--XKC2DL3A5EE0H",
-        "XN--YFRO4I67O",
-        "XN--YGBI2AMMX",
-        "XN--ZFR164B",
-    };
-    // END JOSM PATCH
-
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
     private static final String[] COUNTRY_CODE_TLDS = new String[] {
         "ac",                 // Ascension Island
@@ -749,5 +1238,5 @@
         "al",                 // Albania
         "am",                 // Armenia
-        "an",                 // Netherlands Antilles
+//        "an",                 // Netherlands Antilles (retired)
         "ao",                 // Angola
         "aq",                 // Antarctica
@@ -964,5 +1453,5 @@
         "tn",                 // Tunisia
         "to",                 // Tonga
-        "tp",                 // East Timor
+//        "tp",                 // East Timor (Retired)
         "tr",                 // Turkey
         "tt",                 // Trinidad and Tobago
@@ -973,5 +1462,4 @@
         "ug",                 // Uganda
         "uk",                 // United Kingdom
-        "um",                 // United States Minor Outlying Islands
         "us",                 // United States of America
         "uy",                 // Uruguay
@@ -986,7 +1474,51 @@
         "wf",                 // Wallis and Futuna
         "ws",                 // Samoa (formerly Western Samoa)
+        "xn--3e0b707e", // 한국 KISA (Korea Internet &amp; Security Agency)
+        "xn--45brj9c", // ভারত National Internet Exchange of India
+        "xn--80ao21a", // қаз Association of IT Companies of Kazakhstan
+        "xn--90a3ac", // срб Serbian National Internet Domain Registry (RNIDS)
+        "xn--90ais", // ??? Reliable Software Inc.
+        "xn--clchc0ea0b2g2a9gcd", // சிங்கப்பூர் Singapore Network Information Centre (SGNIC) Pte Ltd
+        "xn--d1alf", // мкд Macedonian Academic Research Network Skopje
+        "xn--e1a4c", // ею EURid vzw/asbl
+        "xn--fiqs8s", // 中国 China Internet Network Information Center
+        "xn--fiqz9s", // 中國 China Internet Network Information Center
+        "xn--fpcrj9c3d", // భారత్ National Internet Exchange of India
+        "xn--fzc2c9e2c", // ලංකා LK Domain Registry
+        "xn--gecrj9c", // ભારત National Internet Exchange of India
+        "xn--h2brj9c", // भारत National Internet Exchange of India
+        "xn--j1amh", // укр Ukrainian Network Information Centre (UANIC), Inc.
+        "xn--j6w193g", // 香港 Hong Kong Internet Registration Corporation Ltd.
+        "xn--kprw13d", // 台湾 Taiwan Network Information Center (TWNIC)
+        "xn--kpry57d", // 台灣 Taiwan Network Information Center (TWNIC)
+        "xn--l1acc", // мон Datacom Co.,Ltd
+        "xn--lgbbat1ad8j", // الجزائر CERIST
+        "xn--mgb9awbf", // عمان Telecommunications Regulatory Authority (TRA)
+        "xn--mgba3a4f16a", // ایران Institute for Research in Fundamental Sciences (IPM)
+        "xn--mgbaam7a8h", // امارات Telecommunications Regulatory Authority (TRA)
+        "xn--mgbayh7gpa", // الاردن National Information Technology Center (NITC)
+        "xn--mgbbh1a71e", // بھارت National Internet Exchange of India
+        "xn--mgbc0a9azcg", // المغرب Agence Nationale de Réglementation des Télécommunications (ANRT)
+        "xn--mgberp4a5d4ar", // السعودية Communications and Information Technology Commission
+        "xn--mgbpl2fh", // ????? Sudan Internet Society
+        "xn--mgbtx2b", // عراق Communications and Media Commission (CMC)
+        "xn--mgbx4cd0ab", // مليسيا MYNIC Berhad
+        "xn--mix891f", // 澳門 Bureau of Telecommunications Regulation (DSRT)
+        "xn--node", // გე Information Technologies Development Center (ITDC)
+        "xn--o3cw4h", // ไทย Thai Network Information Center Foundation
+        "xn--ogbpf8fl", // سورية National Agency for Network Services (NANS)
+        "xn--p1ai", // рф Coordination Center for TLD RU
+        "xn--pgbs0dh", // تونس Agence Tunisienne d&#39;Internet
+        "xn--qxam", // ελ ICS-FORTH GR
+        "xn--s9brj9c", // ਭਾਰਤ National Internet Exchange of India
+        "xn--wgbh1c", // مصر National Telecommunication Regulatory Authority - NTRA
+        "xn--wgbl6a", // قطر Communications Regulatory Authority
+        "xn--xkc2al3hye2a", // இலங்கை LK Domain Registry
+        "xn--xkc2dl3a5ee0h", // இந்தியா National Internet Exchange of India
+        "xn--y9a3aq", // ??? Internet Society
+        "xn--yfro4i67o", // 新加坡 Singapore Network Information Centre (SGNIC) Pte Ltd
+        "xn--ygbi2ammx", // فلسطين Ministry of Telecom &amp; Information Technology (MTIT)
         "ye",                 // Yemen
         "yt",                 // Mayotte
-        "yu",                 // Serbia and Montenegro (originally Yugoslavia)
         "za",                 // South Africa
         "zm",                 // Zambia
@@ -994,15 +1526,243 @@
     };
 
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
     private static final String[] LOCAL_TLDS = new String[] {
+       "localdomain",         // Also widely used as localhost.localdomain
        "localhost",           // RFC2606 defined
-       "localdomain"          // Also widely used as localhost.localdomain
-   };
-
-    static {
-        Arrays.sort(INFRASTRUCTURE_TLDS);
-        Arrays.sort(COUNTRY_CODE_TLDS);
-        Arrays.sort(GENERIC_TLDS);
-        Arrays.sort(IDN_TLDS);
-        Arrays.sort(LOCAL_TLDS);
+    };
+
+    // Additional arrays to supplement or override the built in ones.
+    // The PLUS arrays are valid keys, the MINUS arrays are invalid keys
+
+    /*
+     * This field is used to detect whether the getInstance has been called.
+     * After this, the method updateTLDOverride is not allowed to be called.
+     * This field does not need to be volatile since it is only accessed from
+     * synchronized methods.
+     */
+    private static boolean inUse = false;
+
+    /*
+     * These arrays are mutable, but they don't need to be volatile.
+     * They can only be updated by the updateTLDOverride method, and any readers must get an instance
+     * using the getInstance methods which are all (now) synchronised.
+     */
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
+    private static volatile String[] countryCodeTLDsPlus = EMPTY_STRING_ARRAY;
+
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
+    private static volatile String[] genericTLDsPlus = EMPTY_STRING_ARRAY;
+
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
+    private static volatile String[] countryCodeTLDsMinus = EMPTY_STRING_ARRAY;
+
+    // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search
+    private static volatile String[] genericTLDsMinus = EMPTY_STRING_ARRAY;
+
+    /**
+     * enum used by {@link DomainValidator#updateTLDOverride(ArrayType, String[])}
+     * to determine which override array to update / fetch
+     * @since 1.5.0
+     * @since 1.5.1 made public and added read-only array references
+     */
+    public enum ArrayType {
+        /** Update (or get a copy of) the GENERIC_TLDS_PLUS table containing additonal generic TLDs */
+        GENERIC_PLUS,
+        /** Update (or get a copy of) the GENERIC_TLDS_MINUS table containing deleted generic TLDs */
+        GENERIC_MINUS,
+        /** Update (or get a copy of) the COUNTRY_CODE_TLDS_PLUS table containing additonal country code TLDs */
+        COUNTRY_CODE_PLUS,
+        /** Update (or get a copy of) the COUNTRY_CODE_TLDS_MINUS table containing deleted country code TLDs */
+        COUNTRY_CODE_MINUS,
+        /** Get a copy of the generic TLDS table */
+        GENERIC_RO,
+        /** Get a copy of the country code table */
+        COUNTRY_CODE_RO,
+        /** Get a copy of the infrastructure table */
+        INFRASTRUCTURE_RO,
+        /** Get a copy of the local table */
+        LOCAL_RO
+    }
+
+    // For use by unit test code only
+    static synchronized void clearTLDOverrides() {
+        inUse = false;
+        countryCodeTLDsPlus = EMPTY_STRING_ARRAY;
+        countryCodeTLDsMinus = EMPTY_STRING_ARRAY;
+        genericTLDsPlus = EMPTY_STRING_ARRAY;
+        genericTLDsMinus = EMPTY_STRING_ARRAY;
+    }
+
+    /**
+     * Update one of the TLD override arrays.
+     * This must only be done at program startup, before any instances are accessed using getInstance.
+     * <p>
+     * For example:
+     * <p>
+     * <code>DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"apache"})}</code>
+     * <p>
+     * To clear an override array, provide an empty array.
+     *
+     * @param table the table to update, see {@link DomainValidator.ArrayType}
+     * Must be one of the following
+     * <ul>
+     * <li>COUNTRY_CODE_MINUS</li>
+     * <li>COUNTRY_CODE_PLUS</li>
+     * <li>GENERIC_MINUS</li>
+     * <li>GENERIC_PLUS</li>
+     * </ul>
+     * @param tlds the array of TLDs, must not be null
+     * @throws IllegalStateException if the method is called after getInstance
+     * @throws IllegalArgumentException if one of the read-only tables is requested
+     * @since 1.5.0
+     */
+    public static synchronized void updateTLDOverride(ArrayType table, String[] tlds) {
+        if (inUse) {
+            throw new IllegalStateException("Can only invoke this method before calling getInstance");
+        }
+        String[] copy = new String[tlds.length];
+        // Comparisons are always done with lower-case entries
+        for (int i = 0; i < tlds.length; i++) {
+            copy[i] = tlds[i].toLowerCase(Locale.ENGLISH);
+        }
+        Arrays.sort(copy);
+        switch(table) {
+        case COUNTRY_CODE_MINUS:
+            countryCodeTLDsMinus = copy;
+            break;
+        case COUNTRY_CODE_PLUS:
+            countryCodeTLDsPlus = copy;
+            break;
+        case GENERIC_MINUS:
+            genericTLDsMinus = copy;
+            break;
+        case GENERIC_PLUS:
+            genericTLDsPlus = copy;
+            break;
+        case COUNTRY_CODE_RO:
+        case GENERIC_RO:
+        case INFRASTRUCTURE_RO:
+        case LOCAL_RO:
+            throw new IllegalArgumentException("Cannot update the table: " + table);
+        default:
+            throw new IllegalArgumentException("Unexpected enum value: " + table);
+        }
+    }
+
+    /**
+     * Get a copy of the internal array.
+     * @param table the array type (any of the enum values)
+     * @return a copy of the array
+     * @throws IllegalArgumentException if the table type is unexpected (should not happen)
+     * @since 1.5.1
+     */
+    public static String[] getTLDEntries(ArrayType table) {
+        final String[] array;
+        switch(table) {
+        case COUNTRY_CODE_MINUS:
+            array = countryCodeTLDsMinus;
+            break;
+        case COUNTRY_CODE_PLUS:
+            array = countryCodeTLDsPlus;
+            break;
+        case GENERIC_MINUS:
+            array = genericTLDsMinus;
+            break;
+        case GENERIC_PLUS:
+            array = genericTLDsPlus;
+            break;
+        case GENERIC_RO:
+            array = GENERIC_TLDS;
+            break;
+        case COUNTRY_CODE_RO:
+            array = COUNTRY_CODE_TLDS;
+            break;
+        case INFRASTRUCTURE_RO:
+            array = INFRASTRUCTURE_TLDS;
+            break;
+        case LOCAL_RO:
+            array = LOCAL_TLDS;
+            break;
+        default:
+            throw new IllegalArgumentException("Unexpected enum value: " + table);
+        }
+        return Arrays.copyOf(array, array.length); // clone the array
+    }
+
+    /**
+     * Converts potentially Unicode input to punycode.
+     * If conversion fails, returns the original input.
+     *
+     * @param input the string to convert, not null
+     * @return converted input, or original input if conversion fails
+     */
+    // Needed by UrlValidator
+    static String unicodeToASCII(String input) {
+        if (isOnlyASCII(input)) { // skip possibly expensive processing
+            return input;
+        }
+        try {
+            final String ascii = IDN.toASCII(input);
+            if (IDNBUGHOLDER.IDN_TOASCII_PRESERVES_TRAILING_DOTS) {
+                return ascii;
+            }
+            final int length = input.length();
+            if (length == 0) { // check there is a last character
+                return input;
+            }
+            // RFC3490 3.1. 1)
+            //            Whenever dots are used as label separators, the following
+            //            characters MUST be recognized as dots: U+002E (full stop), U+3002
+            //            (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61
+            //            (halfwidth ideographic full stop).
+            char lastChar = input.charAt(length-1); // fetch original last char
+            switch(lastChar) {
+                case '\u002E': // "." full stop
+                case '\u3002': // ideographic full stop
+                case '\uFF0E': // fullwidth full stop
+                case '\uFF61': // halfwidth ideographic full stop
+                    return ascii + "."; // restore the missing stop
+                default:
+                    return ascii;
+            }
+        } catch (IllegalArgumentException e) { // input is not valid
+            return input;
+        }
+    }
+
+    private static class IDNBUGHOLDER {
+        private static boolean keepsTrailingDot() {
+            final String input = "a."; // must be a valid name
+            return input.equals(IDN.toASCII(input));
+        }
+
+        private static final boolean IDN_TOASCII_PRESERVES_TRAILING_DOTS = keepsTrailingDot();
+    }
+
+    /*
+     * Check if input contains only ASCII
+     * Treats null as all ASCII
+     */
+    private static boolean isOnlyASCII(String input) {
+        if (input == null) {
+            return true;
+        }
+        for (int i = 0; i < input.length(); i++) {
+            if (input.charAt(i) > 0x7F) { // CHECKSTYLE IGNORE MagicNumber
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check if a sorted array contains the specified key
+     *
+     * @param sortedArray the array to search
+     * @param key the key to find
+     * @return {@code true} if the array contains the key
+     */
+    private static boolean arrayContains(String[] sortedArray, String key) {
+        return Arrays.binarySearch(sortedArray, key) >= 0;
     }
 }
Index: /trunk/src/org/openstreetmap/josm/data/validation/routines/EmailValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/routines/EmailValidator.java	(revision 9852)
+++ /trunk/src/org/openstreetmap/josm/data/validation/routines/EmailValidator.java	(revision 9853)
@@ -25,7 +25,4 @@
  * <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
@@ -33,9 +30,7 @@
  * <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: 1608584 $ $Date: 2014-07-07 19:54:07 UTC (Mon, 07 Jul 2014) $
+ * @version $Revision: 1723573 $
  * @since Validator 1.4
  */
@@ -43,19 +38,20 @@
 
     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 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 static final int MAX_USERNAME_LEN = 64;
+
     private final boolean allowLocal;
+    private final boolean allowTld;
 
     /**
@@ -63,5 +59,11 @@
      *  doesn't consider local addresses as valid.
      */
-    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false);
+    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false);
+
+    /**
+     * Singleton instance of this class, which
+     *  doesn't consider local addresses as valid.
+     */
+    private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true);
 
     /**
@@ -69,5 +71,12 @@
      *  consider local addresses valid.
      */
-    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true);
+    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false);
+
+
+    /**
+     * Singleton instance of this class, which does
+     *  consider local addresses valid.
+     */
+    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true);
 
     /**
@@ -85,11 +94,44 @@
      *
      * @param allowLocal Should local addresses be considered valid?
+     * @param allowTld Should TLDs be allowed?
      * @return singleton instance of this validator
      */
+    public static EmailValidator getInstance(boolean allowLocal, boolean allowTld) {
+        if (allowLocal) {
+            if (allowTld) {
+                return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD;
+            } else {
+                return EMAIL_VALIDATOR_WITH_LOCAL;
+            }
+        } else {
+            if (allowTld) {
+                return EMAIL_VALIDATOR_WITH_TLD;
+            } else {
+                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;
+        return getInstance(allowLocal, false);
+    }
+
+    /**
+     * Protected constructor for subclasses to use.
+     *
+     * @param allowLocal Should local addresses be considered valid?
+     * @param allowTld Should TLDs be allowed?
+     */
+    protected EmailValidator(boolean allowLocal, boolean allowTld) {
+        super();
+        this.allowLocal = allowLocal;
+        this.allowTld = allowTld;
     }
 
@@ -102,4 +144,5 @@
         super();
         this.allowLocal = allowLocal;
+        this.allowTld = false;
     }
 
@@ -117,8 +160,6 @@
         }
 
-        Matcher asciiMatcher = MATCH_ASCII_PATTERN.matcher(email);
-        if (!asciiMatcher.matches()) {
-            setErrorMessage(tr("E-mail address contains non-ascii characters"));
-            setFix(email.replaceAll("[^\\p{ASCII}]+", ""));
+        if (email.endsWith(".")) { // check this first - it's cheap!
+            setErrorMessage(tr("E-mail address is invalid"));
             return false;
         }
@@ -131,9 +172,4 @@
         }
 
-        if (email.endsWith(".")) {
-            setErrorMessage(tr("E-mail address is invalid"));
-            return false;
-        }
-
         String username = emailMatcher.group(1);
         if (!isValidUser(username)) {
@@ -154,5 +190,5 @@
      * Returns true if the domain component of an email address is valid.
      *
-     * @param domain being validated.
+     * @param domain being validated, may be in IDN format
      * @return true if the email address's domain is valid.
      */
@@ -165,10 +201,12 @@
                     InetAddressValidator.getInstance();
             return inetAddressValidator.isValid(ipDomainMatcher.group(1));
+        }
+        // Domain is symbolic name
+        DomainValidator domainValidator =
+                DomainValidator.getInstance(allowLocal);
+        if (allowTld) {
+            return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain));
         } else {
-            // Domain is symbolic name
-            DomainValidator domainValidator =
-                    DomainValidator.getInstance(allowLocal);
-            return domainValidator.isValid(domain) ||
-                    domainValidator.isValidTld(domain);
+            return domainValidator.isValid(domain);
         }
     }
@@ -181,4 +219,9 @@
      */
     protected boolean isValidUser(String user) {
+
+        if (user == null || user.length() > MAX_USERNAME_LEN) {
+            return false;
+        }
+
         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 9852)
+++ /trunk/src/org/openstreetmap/josm/data/validation/routines/InetAddressValidator.java	(revision 9853)
@@ -17,4 +17,8 @@
 package org.openstreetmap.josm.data.validation.routines;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * <p><b>InetAddress</b> validation and conversion routines (<code>java.net.InetAddress</code>).</p>
@@ -26,11 +30,23 @@
  * </p>
  *
- * @version $Revision: 1227719 $
+ * @version $Revision: 1715439 $
  * @since Validator 1.4
  */
 public class InetAddressValidator extends AbstractValidator {
 
+    private static final int IPV4_MAX_OCTET_VALUE = 255;
+
+    private static final int MAX_UNSIGNED_SHORT = 0xffff;
+
+    private static final int BASE_16 = 16;
+
     private static final String IPV4_REGEX =
             "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
+
+    // Max number of hex groups (separated by :) in an IPV6 address
+    private static final int IPV6_MAX_HEX_GROUPS = 8;
+
+    // Max hex digits in each IPv6 group
+    private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
 
     /**
@@ -57,5 +73,5 @@
     @Override
     public boolean isValid(String inetAddress) {
-        return isValidInet4Address(inetAddress);
+        return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress);
     }
 
@@ -69,9 +85,10 @@
         String[] groups = ipv4Validator.match(inet4Address);
 
-        if (groups == null) return false;
+        if (groups == null) {
+            return false;
+        }
 
         // verify that address subgroups are legal
-        for (int i = 0; i <= 3; i++) {
-            String ipSegment = groups[i];
+        for (String ipSegment : groups) {
             if (ipSegment == null || ipSegment.isEmpty()) {
                 return false;
@@ -86,5 +103,9 @@
             }
 
-            if (iIpSegment > 255) {
+            if (iIpSegment > IPV4_MAX_OCTET_VALUE) {
+                return false;
+            }
+
+            if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
                 return false;
             }
@@ -94,3 +115,79 @@
         return true;
     }
+
+    /**
+     * Validates an IPv6 address. Returns true if valid.
+     * @param inet6Address the IPv6 address to validate
+     * @return true if the argument contains a valid IPv6 address
+     *
+     * @since 1.4.1
+     */
+    public boolean isValidInet6Address(String inet6Address) {
+        boolean containsCompressedZeroes = inet6Address.contains("::");
+        if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) {
+            return false;
+        }
+        if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::"))
+                || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) {
+            return false;
+        }
+        String[] octets = inet6Address.split(":");
+        if (containsCompressedZeroes) {
+            List<String> octetList = new ArrayList<>(Arrays.asList(octets));
+            if (inet6Address.endsWith("::")) {
+                // String.split() drops ending empty segments
+                octetList.add("");
+            } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) {
+                octetList.remove(0);
+            }
+            octets = octetList.toArray(new String[octetList.size()]);
+        }
+        if (octets.length > IPV6_MAX_HEX_GROUPS) {
+            return false;
+        }
+        int validOctets = 0;
+        int emptyOctets = 0;
+        for (int index = 0; index < octets.length; index++) {
+            String octet = octets[index];
+            if (octet.length() == 0) {
+                emptyOctets++;
+                if (emptyOctets > 1) {
+                    return false;
+                }
+            } else {
+                emptyOctets = 0;
+                if (octet.contains(".")) { // contains is Java 1.5+
+                    if (!inet6Address.endsWith(octet)) {
+                        return false;
+                    }
+                    if (index > octets.length - 1 || index > 6) { // TODO magic number (sort of)
+                        // IPV4 occupies last two octets
+                        return false;
+                    }
+                    if (!isValidInet4Address(octet)) {
+                        return false;
+                    }
+                    validOctets += 2;
+                    continue;
+                }
+                if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
+                    return false;
+                }
+                int octetInt = 0;
+                try {
+                    octetInt = Integer.valueOf(octet, BASE_16).intValue();
+                } catch (NumberFormatException e) {
+                    return false;
+                }
+                if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) {
+                    return false;
+                }
+            }
+            validOctets++;
+        }
+        if (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes) {
+            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 9852)
+++ /trunk/src/org/openstreetmap/josm/data/validation/routines/RegexValidator.java	(revision 9853)
@@ -28,27 +28,41 @@
  * a validator which does <i>case in-sensitive</i> validation for a set of regular
  * expressions:
+ * </p>
  * <pre>
- *         String[] regexs = new String[] {...};
- *         RegexValidator validator = new RegexValidator(regexs, false);
+ * <code>
+ * String[] regexs = new String[] {...};
+ * RegexValidator validator = new RegexValidator(regexs, false);
+ * </code>
  * </pre>
+ *
  * <ul>
- *   <li>Validate <code>true</code> or <code>false</code>:
- *   <ul>
- *     <li><code>boolean valid = validator.isValid(value);</code></li>
- *   </ul></li>
- *   <li>Validate returning an aggregated String of the matched groups:
- *   <ul>
- *     <li><code>String result = validator.validate(value);</code></li>
- *   </ul></li>
- *   <li>Validate returning the matched groups:
- *   <ul>
- *     <li><code>String[] result = validator.match(value);</code></li>
- *   </ul></li>
+ *   <li>Validate <code>true</code> or <code>false</code>:</li>
+ *   <li>
+ *     <ul>
+ *       <li><code>boolean valid = validator.isValid(value);</code></li>
+ *     </ul>
+ *   </li>
+ *   <li>Validate returning an aggregated String of the matched groups:</li>
+ *   <li>
+ *     <ul>
+ *       <li><code>String result = validator.validate(value);</code></li>
+ *     </ul>
+ *   </li>
+ *   <li>Validate returning the matched groups:</li>
+ *   <li>
+ *     <ul>
+ *       <li><code>String[] result = validator.match(value);</code></li>
+ *     </ul>
+ *   </li>
  * </ul>
+ *
+ * <b>Note that patterns are matched against the entire input.</b>
+ *
  * <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) $
+ * </p>
+ *
+ * @version $Revision: 1713331 $
  * @since Validator 1.4
  */
Index: /trunk/src/org/openstreetmap/josm/data/validation/routines/UrlValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/routines/UrlValidator.java	(revision 9852)
+++ /trunk/src/org/openstreetmap/josm/data/validation/routines/UrlValidator.java	(revision 9853)
@@ -19,7 +19,9 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.util.Arrays;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Set;
 import java.util.regex.Matcher;
@@ -28,5 +30,6 @@
 /**
  * <p><b>URL Validation</b> routines.</p>
- * Behavior of validation is modified by passing in options:<ul>
+ * Behavior of validation is modified by passing in options:
+ * <ul>
  * <li>ALLOW_2_SLASHES - [FALSE]  Allows double '/' characters in the path
  * component.</li>
@@ -36,4 +39,5 @@
  * considered valid schemes.  Enabling this option will let any scheme pass validation.</li>
  * </ul>
+ *
  * <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
@@ -64,5 +68,5 @@
  *  </pre>
  *
- * @version $Revision: 1640269 $ $Date: 2014-11-18 02:28:56 UTC (Tue, 18 Nov 2014) $
+ * @version $Revision: 1715435 $
  * @see
  * <a href="http://www.ietf.org/rfc/rfc2396.txt">
@@ -96,8 +100,5 @@
      *  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}\\-\\.";
+    public static final long ALLOW_LOCAL_URLS = 1 << 3; // CHECKSTYLE IGNORE MagicNumber
 
     /**
@@ -106,5 +107,5 @@
     private static final String URL_REGEX =
             "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?";
-
+    //        12            3  4          5       6   7        8 9
     private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX);
 
@@ -126,22 +127,40 @@
 
     /**
-     * Protocol (ie. http:, ftp:,https:).
+     * Protocol scheme (e.g. http, ftp, https).
      */
     private static final String SCHEME_REGEX = "^\\p{Alpha}[\\p{Alnum}\\+\\-\\.]*";
     private static final Pattern SCHEME_PATTERN = Pattern.compile(SCHEME_REGEX);
 
+    // Drop numeric, and  "+-." for now
+    // TODO does not allow for optional userinfo.
+    // Validation of character set is done by isValidAuthority
+    private static final String AUTHORITY_CHARS_REGEX = "\\p{Alnum}\\-\\."; // allows for IPV4 but not IPV6
+    private static final String IPV6_REGEX = "[0-9a-fA-F:]+"; // do this as separate match because : could cause ambiguity with port prefix
+
+    // userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
+    // unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+    // sub-delims    = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
+    // We assume that password has the same valid chars as user info
+    private static final String USERINFO_CHARS_REGEX = "[a-zA-Z0-9%-._~!$&'()*+,;=]";
+    // since neither ':' nor '@' are allowed chars, we don't need to use non-greedy matching
+    private static final String USERINFO_FIELD_REGEX =
+            USERINFO_CHARS_REGEX + "+:" + // At least one character for the name
+            USERINFO_CHARS_REGEX + "*@"; // password may be absent
     private static final String AUTHORITY_REGEX =
-            "^([" + AUTHORITY_CHARS_REGEX + "]*)(:\\d*)?(.*)?";
-
+            "(?:\\[("+IPV6_REGEX+")\\]|(?:(?:"+USERINFO_FIELD_REGEX+")?([" + AUTHORITY_CHARS_REGEX + "]*)))(:\\d*)?(.*)?";
+    //             1                          e.g. user:pass@          2                                   3       4
     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 int PARSE_AUTHORITY_IPV6 = 1;
+
+    private static final int PARSE_AUTHORITY_HOST_IP = 2; // excludes userinfo, if present
+
+    // Not needed, because it is validated by AUTHORITY_REGEX
+//    private static final int PARSE_AUTHORITY_PORT = 3;
+
+    /**
+     * Should always be empty. The code currently allows spaces.
+     */
+    private static final int PARSE_AUTHORITY_EXTRA = 4;
 
     private static final String PATH_REGEX = "^(/[-\\w:@&?=+,.!/~*'%$_;\\(\\)]*)?$";
@@ -151,10 +170,4 @@
     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.
@@ -165,5 +178,5 @@
      * The set of schemes that are allowed to be in a URL.
      */
-    private final Set<String> allowedSchemes;
+    private final Set<String> allowedSchemes; // Must be lower-case
 
     /**
@@ -176,5 +189,5 @@
      * If no schemes are provided, default to this set.
      */
-    private static final String[] DEFAULT_SCHEMES = {"http", "https", "ftp"};
+    private static final String[] DEFAULT_SCHEMES = {"http", "https", "ftp"}; // Must be lower-case
 
     /**
@@ -222,5 +235,5 @@
     /**
      * Behavior of validation is modified by passing in options:
-     * @param schemes The set of valid schemes.
+     * @param schemes The set of valid schemes. Ignored if the ALLOW_ALL_SCHEMES option is set.
      * @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,
@@ -233,4 +246,18 @@
     /**
      * Initialize a UrlValidator with the given validation options.
+     * @param authorityValidator Regular expression validator used to validate the authority part
+     * This allows the user to override the standard set of domains.
+     * @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. Ignored if the ALLOW_ALL_SCHEMES option is set.
      * @param authorityValidator Regular expression validator used to validate the authority part
      * @param options Validation options. Set using the public constants of this class.
@@ -239,36 +266,27 @@
      * 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();
+            allowedSchemes = Collections.emptySet();
         } else {
             if (schemes == null) {
                 schemes = DEFAULT_SCHEMES;
             }
-            this.allowedSchemes = new HashSet<>();
-            this.allowedSchemes.addAll(Arrays.asList(schemes));
+            allowedSchemes = new HashSet<>(schemes.length);
+            for (int i = 0; i < schemes.length; i++) {
+                allowedSchemes.add(schemes[i].toLowerCase(Locale.ENGLISH));
+            }
         }
 
         this.authorityValidator = authorityValidator;
-
     }
 
     /**
      * <p>Checks if a field has a valid url address.</p>
+     *
+     * Note that the method calls #isValidAuthority()
+     * which checks that the domain is valid.
      *
      * @param value The value validation is being performed on.  A <code>null</code>
@@ -282,10 +300,4 @@
         }
 
-        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);
@@ -302,11 +314,17 @@
 
         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;
+        if ("file".equals(scheme)) { // Special case - file: allows an empty authority
+            if (!"".equals(authority)) {
+                if (authority.contains(":")) { // but cannot allow trailing :
+                    setErrorMessage(tr("URL contains an invalid authority: {0}", authority));
+                    return false;
+                }
+            }
+            // drop through to continue validation
+        } else { // not file:
+            // Validate the authority
+            if (!isValidAuthority(authority)) {
+                setErrorMessage(tr("URL contains an invalid authority: {0}", authority));
+                return false;
             }
         }
@@ -335,6 +353,7 @@
     /**
      * 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.
+     * then only those schemes are allowed.
+     * Otherwise the default schemes are "http", "https", "ftp".
+     * Matching is case-blind.
      * @param scheme The scheme to validate.  A <code>null</code> value is considered
      * invalid.
@@ -346,13 +365,11 @@
         }
 
+        // TODO could be removed if external schemes were checked in the ctor before being stored
         if (!SCHEME_PATTERN.matcher(scheme).matches()) {
             return false;
         }
 
-        if (isOff(ALLOW_ALL_SCHEMES)) {
-
-            if (!this.allowedSchemes.contains(scheme)) {
-                return false;
-            }
+        if (isOff(ALLOW_ALL_SCHEMES) && !allowedSchemes.contains(scheme.toLowerCase(Locale.ENGLISH))) {
+            return false;
         }
 
@@ -363,5 +380,9 @@
      * 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.
+     * Note: this implementation validates the domain unless a RegexValidator was provided.
+     * If a RegexValidator was supplied and it matches, then the authority is regarded
+     * as valid with no further checks, otherwise the method checks against the
+     * AUTHORITY_PATTERN and the DomainValidator (ALLOW_LOCAL_URLS)
+     * @param authority Authority value to validate, alllows IDN
      * @return true if authority (hostname and port) is valid.
      */
@@ -372,33 +393,34 @@
 
         // check manual authority validation if specified
-        if (authorityValidator != null) {
-            if (authorityValidator.isValid(authority)) {
-                return true;
-            }
-        }
-
-        Matcher authorityMatcher = AUTHORITY_PATTERN.matcher(authority);
+        if (authorityValidator != null && authorityValidator.isValid(authority)) {
+            return true;
+        }
+        // convert to ASCII if possible
+        final String authorityASCII = DomainValidator.unicodeToASCII(authority);
+
+        Matcher authorityMatcher = AUTHORITY_PATTERN.matcher(authorityASCII);
         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;
+        // We have to process IPV6 separately because that is parsed in a different group
+        String ipv6 = authorityMatcher.group(PARSE_AUTHORITY_IPV6);
+        if (ipv6 != null) {
+            InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance();
+                if (!inetAddressValidator.isValidInet6Address(ipv6)) {
+                    return false;
+                }
+        } else {
+            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 IPv4 address
+                InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance();
+                if (!inetAddressValidator.isValidInet4Address(hostLocation)) {
+                    // isn't IPv4, so the URL is invalid
+                    return false;
+                }
             }
         }
@@ -426,15 +448,18 @@
         }
 
+        try {
+            URI uri = new URI(null, null, path, null);
+            String norm = uri.normalize().getPath();
+            if (norm.startsWith("/../") // Trying to go via the parent dir
+             || norm.equals("/..")) {   // Trying to go to the parent dir
+                return false;
+            }
+        } catch (URISyntaxException e) {
+            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;
-            }
         }
 
@@ -496,5 +521,5 @@
      */
     private boolean isOn(long flag) {
-        return (this.options & flag) > 0;
+        return (options & flag) > 0;
     }
 
@@ -508,5 +533,10 @@
      */
     private boolean isOff(long flag) {
-        return (this.options & flag) == 0;
+        return (options & flag) == 0;
+    }
+
+    // Unit test access to pattern matcher
+    Matcher matchURL(String value) {
+        return URL_PATTERN.matcher(value);
     }
 }
Index: /trunk/src/org/openstreetmap/josm/data/validation/routines/package.html
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/routines/package.html	(revision 9852)
+++ /trunk/src/org/openstreetmap/josm/data/validation/routines/package.html	(revision 9853)
@@ -20,5 +20,5 @@
 </head>
 <body bgcolor="white">
-    <p>This package contains <i>independant</i> validation routines adapted from Apache Commons Validator 1.4.0.</p>
+    <p>This package contains <i>independant</i> validation routines adapted from Apache Commons Validator 1.5.0.</p>
 <h1>Table of Contents</h1>
 
@@ -36,5 +36,5 @@
 </ul>
 
-<a id="overview"></a>
+<a name="overview"></a>
 <h1>1. Overview</h1>
 <p>
@@ -42,10 +42,10 @@
 </p>
     <ul>
-       <li>To provide standard, independant validation routines/functions.</li>
+       <li>To provide standard, independent 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
+   separate these two concerns and is the location for the standard, independent
    validation routines/functions in <em>Commons Validator</em>.
 </p>
@@ -56,8 +56,8 @@
 </p>
 
-<a id="other"></a>
+<a name="other"></a>
 <h1>2. Validators</h1>
 
-<a id="other.overview"></a>
+<a name="other.overview"></a>
 <h3>2.1 Overview</h3>
 <p>
@@ -77,5 +77,5 @@
 </ul>
 
-<a id="other.regex"></a>
+<a name="other.regex"></a>
 <h3>2.2 Regular Expression Validation</h3>
 <p>
@@ -157,5 +157,5 @@
 </p>
 
-<a id="other.inet"></a>
+<a name="other.inet"></a>
 <h3>2.3 IP Address Validation</h3>
 
@@ -179,5 +179,5 @@
 </pre>
 
-<a id="other.email"></a>
+<a name="other.email"></a>
 <h3>2.4 Email Address Validation</h3>
 
@@ -203,5 +203,5 @@
 </pre>
 
-<a id="other.url"></a>
+<a name="other.url"></a>
 <h3>2.5 URL Validation</h3>
 
@@ -244,5 +244,5 @@
 </pre>
 
-<a id="other.domain"></a>
+<a name="other.domain"></a>
 <h3>2.6 Domain Name Validation</h3>
 
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/DomainValidatorIT.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/DomainValidatorIT.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/DomainValidatorIT.java	(revision 9853)
@@ -0,0 +1,390 @@
+/*
+ * 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.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.HttpURLConnection;
+import java.net.IDN;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+/**
+ * Integration tests for the DomainValidator.
+ *
+ * @version $Revision: 1723861 $
+ */
+public class DomainValidatorIT {
+
+    /**
+     * Download and process local copy of http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     * Check if the internal TLD table is up to date
+     * Check if the internal TLD tables have any spurious entries
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testIanaTldList() throws Exception {
+        // Check the arrays first as this affects later checks
+        // Doing this here makes it easier when updating the lists
+        boolean OK = true;
+        for (String list : new String[]{"INFRASTRUCTURE_TLDS", "COUNTRY_CODE_TLDS", "GENERIC_TLDS", "LOCAL_TLDS"}) {
+            OK &= isSortedLowerCase(list);
+        }
+        if (!OK) {
+            System.out.println("Fix arrays before retrying; cannot continue");
+            return;
+        }
+        Set<String> ianaTlds = new HashSet<>(); // keep for comparison with array contents
+        DomainValidator dv = DomainValidator.getInstance();
+        File txtFile = new File(System.getProperty("java.io.tmpdir"), "tlds-alpha-by-domain.txt");
+        long timestamp = download(txtFile, "http://data.iana.org/TLD/tlds-alpha-by-domain.txt", 0L);
+        final File htmlFile = new File(System.getProperty("java.io.tmpdir"), "tlds-alpha-by-domain.html");
+        // N.B. sometimes the html file may be updated a day or so after the txt file
+        // if the txt file contains entries not found in the html file, try again in a day or two
+        download(htmlFile, "http://www.iana.org/domains/root/db", timestamp);
+
+        BufferedReader br = new BufferedReader(new FileReader(txtFile));
+        String line;
+        final String header;
+        line = br.readLine(); // header
+        if (line != null && line.startsWith("# Version ")) {
+            header = line.substring(2);
+        } else {
+            br.close();
+            throw new IOException("File does not have expected Version header");
+        }
+        final boolean generateUnicodeTlds = false; // Change this to generate Unicode TLDs as well
+
+        // Parse html page to get entries
+        Map<String, String[]> htmlInfo = getHtmlInfo(htmlFile);
+        Map<String, String> missingTLD = new TreeMap<>(); // stores entry and comments as String[]
+        Map<String, String> missingCC = new TreeMap<>();
+        while ((line = br.readLine()) != null) {
+            if (!line.startsWith("#")) {
+                final String unicodeTld; // only different from asciiTld if that was punycode
+                final String asciiTld = line.toLowerCase(Locale.ENGLISH);
+                if (line.startsWith("XN--")) {
+                    unicodeTld = IDN.toUnicode(line);
+                } else {
+                    unicodeTld = asciiTld;
+                }
+                if (!dv.isValidTld(asciiTld)) {
+                    String[] info = htmlInfo.get(asciiTld);
+                    if (info != null) {
+                        String type = info[0];
+                        String comment = info[1];
+                        if ("country-code".equals(type)) { // Which list to use?
+                            missingCC.put(asciiTld, unicodeTld + " " + comment);
+                            if (generateUnicodeTlds) {
+                                missingCC.put(unicodeTld, asciiTld + " " + comment);
+                            }
+                        } else {
+                            missingTLD.put(asciiTld, unicodeTld + " " + comment);
+                            if (generateUnicodeTlds) {
+                                missingTLD.put(unicodeTld, asciiTld + " " + comment);
+                            }
+                        }
+                    } else {
+                        System.err.println("Expected to find HTML info for "+ asciiTld);
+                    }
+                }
+                ianaTlds.add(asciiTld);
+                // Don't merge these conditions; generateUnicodeTlds is final so needs to be separate to avoid a warning
+                if (generateUnicodeTlds) {
+                    if (!unicodeTld.equals(asciiTld)) {
+                        ianaTlds.add(unicodeTld);
+                    }
+                }
+            }
+        }
+        br.close();
+        // List html entries not in TLD text list
+        for (String key : (new TreeMap<>(htmlInfo)).keySet()) {
+            if (!ianaTlds.contains(key)) {
+                if (isNotInRootZone(key)) {
+                    System.out.println("INFO: HTML entry not yet in root zone: "+key);
+                } else {
+                    System.err.println("WARN: Expected to find text entry for html: "+key);
+                }
+            }
+        }
+        if (!missingTLD.isEmpty()) {
+            printMap(header, missingTLD, "TLD");
+            fail("missing TLD");
+        }
+        if (!missingCC.isEmpty()) {
+            printMap(header, missingCC, "CC");
+            fail("missing CC");
+        }
+        // Check if internal tables contain any additional entries
+        assertTrue(isInIanaList("INFRASTRUCTURE_TLDS", ianaTlds));
+        assertTrue(isInIanaList("COUNTRY_CODE_TLDS", ianaTlds));
+        assertTrue(isInIanaList("GENERIC_TLDS", ianaTlds));
+        // Don't check local TLDS assertTrue(isInIanaList("LOCAL_TLDS", ianaTlds));
+    }
+
+    private static void printMap(final String header, Map<String, String> map, String string) {
+        System.out.println("Entries missing from "+ string +" List\n");
+        if (header != null) {
+            System.out.println("        // Taken from " + header);
+        }
+        Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, String> me = it.next();
+            System.out.println("        \"" + me.getKey() + "\", // " + me.getValue());
+        }
+        System.out.println("\nDone");
+    }
+
+    private static Map<String, String[]> getHtmlInfo(final File f) throws IOException {
+        final Map<String, String[]> info = new HashMap<>();
+
+//        <td><span class="domain tld"><a href="/domains/root/db/ax.html">.ax</a></span></td>
+        final Pattern domain = Pattern.compile(".*<a href=\"/domains/root/db/([^.]+)\\.html");
+//        <td>country-code</td>
+        final Pattern type = Pattern.compile("\\s+<td>([^<]+)</td>");
+//        <!-- <td>Åland Islands<br/><span class="tld-table-so">Ålands landskapsregering</span></td> </td> -->
+//        <td>Ålands landskapsregering</td>
+        final Pattern comment = Pattern.compile("\\s+<td>([^<]+)</td>");
+
+        final BufferedReader br = new BufferedReader(new FileReader(f));
+        String line;
+        while ((line = br.readLine()) != null) {
+            Matcher m = domain.matcher(line);
+            if (m.lookingAt()) {
+                String dom = m.group(1);
+                String typ = "??";
+                String com = "??";
+                line = br.readLine();
+                while (line != null && line.matches("^\\s*$")) { // extra blank lines introduced
+                    line = br.readLine();
+                }
+                Matcher t = type.matcher(line);
+                if (t.lookingAt()) {
+                    typ = t.group(1);
+                    line = br.readLine();
+                    if (line != null && line.matches("\\s+<!--.*")) {
+                        while (line != null && !line.matches(".*-->.*")) {
+                            line = br.readLine();
+                        }
+                        line = br.readLine();
+                    }
+                    // Should have comment; is it wrapped?
+                    while (line != null && !line.matches(".*</td>.*")) {
+                        line += " " +br.readLine();
+                    }
+                    Matcher n = comment.matcher(line);
+                    if (n.lookingAt()) {
+                        com = n.group(1);
+                    }
+                    // Don't save unused entries
+                    if (com.contains("Not assigned") || com.contains("Retired") || typ.equals("test")) {
+//                        System.out.println("Ignored: " + typ + " " + dom + " " +com);
+                    } else {
+                        info.put(dom.toLowerCase(Locale.ENGLISH), new String[]{typ, com});
+//                        System.out.println("Storing: " + typ + " " + dom + " " +com);
+                    }
+                } else {
+                    System.err.println("Unexpected type: " + line);
+                }
+            }
+        }
+        br.close();
+        return info;
+    }
+
+    /*
+     * Download a file if it is more recent than our cached copy.
+     * Unfortunately the server does not seem to honour If-Modified-Since for the
+     * Html page, so we check if it is newer than the txt file and skip download if so
+     */
+    private static long download(File f, String tldurl, long timestamp) throws IOException {
+        final int HOUR = 60*60*1000; // an hour in ms
+        final long modTime;
+        // For testing purposes, don't download files more than once an hour
+        if (f.canRead()) {
+            modTime = f.lastModified();
+            if (modTime > System.currentTimeMillis()-HOUR) {
+                System.out.println("Skipping download - found recent " + f);
+                return modTime;
+            }
+        } else {
+            modTime = 0;
+        }
+        HttpURLConnection hc = (HttpURLConnection) new URL(tldurl).openConnection();
+        if (modTime > 0) {
+            SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z"); //Sun, 06 Nov 1994 08:49:37 GMT
+            String since = sdf.format(new Date(modTime));
+            hc.addRequestProperty("If-Modified-Since", since);
+            System.out.println("Found " + f + " with date " + since);
+        }
+        if (hc.getResponseCode() == 304) {
+            System.out.println("Already have most recent " + tldurl);
+        } else {
+            System.out.println("Downloading " + tldurl);
+            byte[] buff = new byte[1024];
+            InputStream is = hc.getInputStream();
+
+            FileOutputStream fos = new FileOutputStream(f);
+            int len;
+            while ((len = is.read(buff)) != -1) {
+                fos.write(buff, 0, len);
+            }
+            fos.close();
+            is.close();
+            System.out.println("Done");
+        }
+        return f.lastModified();
+    }
+
+    /**
+     * Check whether the domain is in the root zone currently.
+     * Reads the URL http://www.iana.org/domains/root/db/*domain*.html
+     * (using a local disk cache)
+     * and checks for the string "This domain is not present in the root zone at this time."
+     * @param domain the domain to check
+     * @return true if the string is found
+     */
+    private static boolean isNotInRootZone(String domain) {
+        String tldurl = "http://www.iana.org/domains/root/db/" + domain + ".html";
+        BufferedReader in = null;
+        try {
+            File rootCheck = new File(System.getProperty("java.io.tmpdir"), "tld_" + domain + ".html");
+            download(rootCheck, tldurl, 0L);
+            in = new BufferedReader(new FileReader(rootCheck));
+            String inputLine;
+            while ((inputLine = in.readLine()) != null) {
+                if (inputLine.contains("This domain is not present in the root zone at this time.")) {
+                    return true;
+                }
+            }
+            in.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            closeQuietly(in);
+        }
+        return false;
+    }
+
+    private static void closeQuietly(Closeable in) {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    // isInIanaList and isSorted are split into two methods.
+    // If/when access to the arrays is possible without reflection, the intermediate
+    // methods can be dropped
+    private static boolean isInIanaList(String arrayName, Set<String> ianaTlds) throws Exception {
+        Field f = DomainValidator.class.getDeclaredField(arrayName);
+        final boolean isPrivate = Modifier.isPrivate(f.getModifiers());
+        if (isPrivate) {
+            f.setAccessible(true);
+        }
+        String[] array = (String[]) f.get(null);
+        try {
+            return isInIanaList(arrayName, array, ianaTlds);
+        } finally {
+            if (isPrivate) {
+                f.setAccessible(false);
+            }
+        }
+    }
+
+    private static boolean isInIanaList(String name, String[] array, Set<String> ianaTlds) {
+        boolean ok = true;
+        for (int i = 0; i < array.length; i++) {
+            if (!ianaTlds.contains(array[i])) {
+                System.out.println(name + " contains unexpected value: " + array[i]);
+                ok = false;
+            }
+        }
+        return ok;
+    }
+
+    private static boolean isSortedLowerCase(String arrayName) throws Exception {
+        Field f = DomainValidator.class.getDeclaredField(arrayName);
+        final boolean isPrivate = Modifier.isPrivate(f.getModifiers());
+        if (isPrivate) {
+            f.setAccessible(true);
+        }
+        String[] array = (String[]) f.get(null);
+        try {
+            return isSortedLowerCase(arrayName, array);
+        } finally {
+            if (isPrivate) {
+                f.setAccessible(false);
+            }
+        }
+    }
+
+    private static boolean isLowerCase(String string) {
+        return string.equals(string.toLowerCase(Locale.ENGLISH));
+    }
+
+    // Check if an array is strictly sorted - and lowerCase
+    private static boolean isSortedLowerCase(String name, String[] array) {
+        boolean sorted = true;
+        boolean strictlySorted = true;
+        final int length = array.length;
+        boolean lowerCase = isLowerCase(array[length-1]); // Check the last entry
+        for (int i = 0; i < length-1; i++) { // compare all but last entry with next
+            final String entry = array[i];
+            final String nextEntry = array[i+1];
+            final int cmp = entry.compareTo(nextEntry);
+            if (cmp > 0) { // out of order
+                System.out.println("Out of order entry: " + entry + " < " + nextEntry + " in " + name);
+                sorted = false;
+            } else if (cmp == 0) {
+                strictlySorted = false;
+                System.out.println("Duplicated entry: " + entry + " in " + name);
+            }
+            if (!isLowerCase(entry)) {
+                System.out.println("Non lowerCase entry: " + entry + " in " + name);
+                lowerCase = false;
+            }
+        }
+        return sorted && strictlySorted && lowerCase;
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/DomainValidatorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/DomainValidatorTest.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/DomainValidatorTest.java	(revision 9853)
@@ -0,0 +1,510 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.IDN;
+import java.util.Locale;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.validation.routines.DomainValidator.ArrayType;
+
+/**
+ * Tests for the DomainValidator.
+ *
+ * @version $Revision: 1723861 $
+ */
+public class DomainValidatorTest {
+
+    private DomainValidator validator;
+
+    /**
+     * Setup test.
+     */
+    @Before
+    public void setUp() {
+        validator = DomainValidator.getInstance();
+        DomainValidator.clearTLDOverrides(); // N.B. this clears the inUse flag, allowing overrides
+    }
+
+    /**
+     * Test valid domains.
+     */
+    @Test
+    public void testValidDomains() {
+        assertTrue("apache.org should validate", validator.isValid("apache.org"));
+        assertTrue("www.google.com should validate", validator.isValid("www.google.com"));
+
+        assertTrue("test-domain.com should validate", validator.isValid("test-domain.com"));
+        assertTrue("test---domain.com should validate", validator.isValid("test---domain.com"));
+        assertTrue("test-d-o-m-ain.com should validate", validator.isValid("test-d-o-m-ain.com"));
+        assertTrue("two-letter domain label should validate", validator.isValid("as.uk"));
+
+        assertTrue("case-insensitive ApAchE.Org should validate", validator.isValid("ApAchE.Org"));
+
+        assertTrue("single-character domain label should validate", validator.isValid("z.com"));
+
+        assertTrue("i.have.an-example.domain.name should validate", validator.isValid("i.have.an-example.domain.name"));
+    }
+
+    /**
+     * Test invalid domains.
+     */
+    @Test
+    public void testInvalidDomains() {
+        assertFalse("bare TLD .org shouldn't validate", validator.isValid(".org"));
+        assertFalse("domain name with spaces shouldn't validate", validator.isValid(" apache.org "));
+        assertFalse("domain name containing spaces shouldn't validate", validator.isValid("apa che.org"));
+        assertFalse("domain name starting with dash shouldn't validate", validator.isValid("-testdomain.name"));
+        assertFalse("domain name ending with dash shouldn't validate", validator.isValid("testdomain-.name"));
+        assertFalse("domain name starting with multiple dashes shouldn't validate", validator.isValid("---c.com"));
+        assertFalse("domain name ending with multiple dashes shouldn't validate", validator.isValid("c--.com"));
+        assertFalse("domain name with invalid TLD shouldn't validate", validator.isValid("apache.rog"));
+
+        assertFalse("URL shouldn't validate", validator.isValid("http://www.apache.org"));
+        assertFalse("Empty string shouldn't validate as domain name", validator.isValid(" "));
+        assertFalse("Null shouldn't validate as domain name", validator.isValid(null));
+    }
+
+    /**
+     * Test top-level domains.
+     */
+    @Test
+    public void testTopLevelDomains() {
+        // infrastructure TLDs
+        assertTrue(".arpa should validate as iTLD", validator.isValidInfrastructureTld(".arpa"));
+        assertFalse(".com shouldn't validate as iTLD", validator.isValidInfrastructureTld(".com"));
+
+        // generic TLDs
+        assertTrue(".name should validate as gTLD", validator.isValidGenericTld(".name"));
+        assertFalse(".us shouldn't validate as gTLD", validator.isValidGenericTld(".us"));
+
+        // country code TLDs
+        assertTrue(".uk should validate as ccTLD", validator.isValidCountryCodeTld(".uk"));
+        assertFalse(".org shouldn't validate as ccTLD", validator.isValidCountryCodeTld(".org"));
+
+        // case-insensitive
+        assertTrue(".COM should validate as TLD", validator.isValidTld(".COM"));
+        assertTrue(".BiZ should validate as TLD", validator.isValidTld(".BiZ"));
+
+        // corner cases
+        assertFalse("invalid TLD shouldn't validate", validator.isValid(".nope")); // TODO this is not guaranteed invalid forever
+        assertFalse("empty string shouldn't validate as TLD", validator.isValid(""));
+        assertFalse("null shouldn't validate as TLD", validator.isValid(null));
+    }
+
+    /**
+     * Test "allow local" parameter.
+     */
+    @Test
+    public void testAllowLocal() {
+       DomainValidator noLocal = DomainValidator.getInstance(false);
+       DomainValidator allowLocal = DomainValidator.getInstance(true);
+
+       // Default is false, and should use singletons
+       assertEquals(noLocal, validator);
+
+       // Default won't allow local
+       assertFalse("localhost.localdomain should validate", noLocal.isValid("localhost.localdomain"));
+       assertFalse("localhost should validate", noLocal.isValid("localhost"));
+
+       // But it may be requested
+       assertTrue("localhost.localdomain should validate", allowLocal.isValid("localhost.localdomain"));
+       assertTrue("localhost should validate", allowLocal.isValid("localhost"));
+       assertTrue("hostname should validate", allowLocal.isValid("hostname"));
+       assertTrue("machinename should validate", allowLocal.isValid("machinename"));
+
+       // Check the localhost one with a few others
+       assertTrue("apache.org should validate", allowLocal.isValid("apache.org"));
+       assertFalse("domain name with spaces shouldn't validate", allowLocal.isValid(" apache.org "));
+    }
+
+    /**
+     * Test IDN.
+     */
+    @Test
+    public void testIDN() {
+       assertTrue("b\u00fccher.ch in IDN should validate", validator.isValid("www.xn--bcher-kva.ch"));
+    }
+
+    /**
+     * Test IDN with Java >= 6.
+     */
+    @Test
+    public void testIDNJava6OrLater() {
+        String version = System.getProperty("java.version");
+        if (version.compareTo("1.6") < 0) {
+            System.out.println("Cannot run Unicode IDN tests");
+            return; // Cannot run the test
+        } // xn--d1abbgf6aiiy.xn--p1ai http://президент.рф
+       assertTrue("b\u00fccher.ch should validate", validator.isValid("www.b\u00fccher.ch"));
+       assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("xn--d1abbgf6aiiy.xn--p1ai"));
+       assertTrue("президент.рф should validate", validator.isValid("президент.рф"));
+       assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("www.\uFFFD.ch"));
+    }
+
+    /**
+     * RFC2396: domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
+     */
+    @Test
+    public void testRFC2396domainlabel() { // use fixed valid TLD
+        assertTrue("a.ch should validate", validator.isValid("a.ch"));
+        assertTrue("9.ch should validate", validator.isValid("9.ch"));
+        assertTrue("az.ch should validate", validator.isValid("az.ch"));
+        assertTrue("09.ch should validate", validator.isValid("09.ch"));
+        assertTrue("9-1.ch should validate", validator.isValid("9-1.ch"));
+        assertFalse("91-.ch should not validate", validator.isValid("91-.ch"));
+        assertFalse("-.ch should not validate", validator.isValid("-.ch"));
+    }
+
+    /**
+     * RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+     */
+    @Test
+    public void testRFC2396toplabel() {
+        // These tests use non-existent TLDs so currently need to use a package protected method
+        assertTrue("a.c (alpha) should validate", validator.isValidDomainSyntax("a.c"));
+        assertTrue("a.cc (alpha alpha) should validate", validator.isValidDomainSyntax("a.cc"));
+        assertTrue("a.c9 (alpha alphanum) should validate", validator.isValidDomainSyntax("a.c9"));
+        assertTrue("a.c-9 (alpha - alphanum) should validate", validator.isValidDomainSyntax("a.c-9"));
+        assertTrue("a.c-z (alpha - alpha) should validate", validator.isValidDomainSyntax("a.c-z"));
+
+        assertFalse("a.9c (alphanum alpha) should fail", validator.isValidDomainSyntax("a.9c"));
+        assertFalse("a.c- (alpha -) should fail", validator.isValidDomainSyntax("a.c-"));
+        assertFalse("a.- (-) should fail", validator.isValidDomainSyntax("a.-"));
+        assertFalse("a.-9 (- alphanum) should fail", validator.isValidDomainSyntax("a.-9"));
+    }
+
+    /**
+     * rfc1123
+     */
+    @Test
+    public void testDomainNoDots() {
+        assertTrue("a (alpha) should validate", validator.isValidDomainSyntax("a"));
+        assertTrue("9 (alphanum) should validate", validator.isValidDomainSyntax("9"));
+        assertTrue("c-z (alpha - alpha) should validate", validator.isValidDomainSyntax("c-z"));
+
+        assertFalse("c- (alpha -) should fail", validator.isValidDomainSyntax("c-"));
+        assertFalse("-c (- alpha) should fail", validator.isValidDomainSyntax("-c"));
+        assertFalse("- (-) should fail", validator.isValidDomainSyntax("-"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-297
+     */
+    @Test
+    public void testValidator297() {
+        assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("xn--d1abbgf6aiiy.xn--p1ai")); // This uses a valid TLD
+     }
+
+    /**
+     * Non-regression test for VALIDATOR-306
+     * labels are a max of 63 chars and domains 253
+     */
+    @Test
+    public void testValidator306() {
+        final String longString = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789A";
+        assertEquals(63, longString.length()); // 26 * 2 + 11
+
+        assertTrue("63 chars label should validate", validator.isValidDomainSyntax(longString+".com"));
+        assertFalse("64 chars label should fail", validator.isValidDomainSyntax(longString+"x.com"));
+
+        assertTrue("63 chars TLD should validate", validator.isValidDomainSyntax("test."+longString));
+        assertFalse("64 chars TLD should fail", validator.isValidDomainSyntax("test.x"+longString));
+
+        final String longDomain =
+                longString
+                + "." + longString
+                + "." + longString
+                + "." + longString.substring(0, 61);
+        assertEquals(253, longDomain.length());
+        assertTrue("253 chars domain should validate", validator.isValidDomainSyntax(longDomain));
+        assertFalse("254 chars domain should fail", validator.isValidDomainSyntax(longDomain+"x"));
+    }
+
+    /**
+     *  Check that IDN.toASCII behaves as it should (when wrapped by DomainValidator.unicodeToASCII)
+     *  Tests show that method incorrectly trims a trailing "." character
+     */
+    @Test
+    public void testUnicodeToASCII() {
+        String[] asciidots = {
+                "",
+                ",",
+                ".", // fails IDN.toASCII, but should pass wrapped version
+                "a.", // ditto
+                "a.b",
+                "a..b",
+                "a...b",
+                ".a",
+                "..a",
+        };
+        for (String s : asciidots) {
+            assertEquals(s, DomainValidator.unicodeToASCII(s));
+        }
+        // RFC3490 3.1. 1)
+//      Whenever dots are used as label separators, the following
+//      characters MUST be recognized as dots: U+002E (full stop), U+3002
+//      (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61
+//      (halfwidth ideographic full stop).
+        final String[][] otherDots = {
+                {"b\u3002", "b."},
+                {"b\uFF0E", "b."},
+                {"b\uFF61", "b."},
+                {"\u3002", "."},
+                {"\uFF0E", "."},
+                {"\uFF61", "."},
+        };
+        for (String[] s : otherDots) {
+            assertEquals(s[1], DomainValidator.unicodeToASCII(s[0]));
+        }
+    }
+
+    /**
+     * Check if IDN.toASCII is broken or not
+     */
+    @Test
+    public void testIsIDNtoASCIIBroken() {
+        System.out.println(">>DomainValidatorTest.testIsIDNtoASCIIBroken()");
+        final String input = ".";
+        final boolean ok = input.equals(IDN.toASCII(input));
+        System.out.println("IDN.toASCII is " + (ok ? "OK" : "BROKEN"));
+        String[] props = {
+        "java.version", //    Java Runtime Environment version
+        "java.vendor", // Java Runtime Environment vendor
+        "java.vm.specification.version", //   Java Virtual Machine specification version
+        "java.vm.specification.vendor", //    Java Virtual Machine specification vendor
+        "java.vm.specification.name", //  Java Virtual Machine specification name
+        "java.vm.version", // Java Virtual Machine implementation version
+        "java.vm.vendor", //  Java Virtual Machine implementation vendor
+        "java.vm.name", //    Java Virtual Machine implementation name
+        "java.specification.version", //  Java Runtime Environment specification version
+        "java.specification.vendor", //   Java Runtime Environment specification vendor
+        "java.specification.name", // Java Runtime Environment specification name
+        "java.class.version", //  Java class format version number
+        };
+        for (String t : props) {
+            System.out.println(t + "=" + System.getProperty(t));
+        }
+        System.out.println("<<DomainValidatorTest.testIsIDNtoASCIIBroken()");
+    }
+
+    /**
+     * Check array is sorted and is lower-case
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void test_INFRASTRUCTURE_TLDS_sortedAndLowerCase() throws Exception {
+        final boolean sorted = isSortedLowerCase("INFRASTRUCTURE_TLDS");
+        assertTrue(sorted);
+    }
+
+    /**
+     * Check array is sorted and is lower-case
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void test_COUNTRY_CODE_TLDS_sortedAndLowerCase() throws Exception {
+        final boolean sorted = isSortedLowerCase("COUNTRY_CODE_TLDS");
+        assertTrue(sorted);
+    }
+
+    /**
+     * Check array is sorted and is lower-case
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void test_GENERIC_TLDS_sortedAndLowerCase() throws Exception {
+        final boolean sorted = isSortedLowerCase("GENERIC_TLDS");
+        assertTrue(sorted);
+    }
+
+    /**
+     * Check array is sorted and is lower-case
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void test_LOCAL_TLDS_sortedAndLowerCase() throws Exception {
+        final boolean sorted = isSortedLowerCase("LOCAL_TLDS");
+        assertTrue(sorted);
+    }
+
+    /**
+     * Test enum visibility
+     */
+    @Test
+    public void testEnumIsPublic() {
+        assertTrue(Modifier.isPublic(DomainValidator.ArrayType.class.getModifiers()));
+    }
+
+    /**
+     * Test update base arrays
+     */
+    @Test
+    public void testUpdateBaseArrays() {
+        try {
+            DomainValidator.updateTLDOverride(ArrayType.COUNTRY_CODE_RO, new String[]{"com"});
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // expected
+            Main.debug(iae.getMessage());
+        }
+        try {
+            DomainValidator.updateTLDOverride(ArrayType.GENERIC_RO, new String[]{"com"});
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // expected
+            Main.debug(iae.getMessage());
+        }
+        try {
+            DomainValidator.updateTLDOverride(ArrayType.INFRASTRUCTURE_RO, new String[]{"com"});
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // expected
+            Main.debug(iae.getMessage());
+        }
+        try {
+            DomainValidator.updateTLDOverride(ArrayType.LOCAL_RO, new String[]{"com"});
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // expected
+            Main.debug(iae.getMessage());
+        }
+    }
+
+    /**
+     * Test get array.
+     */
+    @Test
+    public void testGetArray() {
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.COUNTRY_CODE_MINUS));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.COUNTRY_CODE_PLUS));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.GENERIC_MINUS));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.GENERIC_PLUS));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.COUNTRY_CODE_RO));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.GENERIC_RO));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.INFRASTRUCTURE_RO));
+        assertNotNull(DomainValidator.getTLDEntries(ArrayType.LOCAL_RO));
+    }
+
+    /**
+     * Test update country code.
+     */
+    @Test
+    public void testUpdateCountryCode() {
+        assertFalse(validator.isValidCountryCodeTld("com")); // cannot be valid
+        DomainValidator.updateTLDOverride(ArrayType.COUNTRY_CODE_PLUS, new String[]{"com"});
+        assertTrue(validator.isValidCountryCodeTld("com")); // it is now!
+        DomainValidator.updateTLDOverride(ArrayType.COUNTRY_CODE_MINUS, new String[]{"com"});
+        assertFalse(validator.isValidCountryCodeTld("com")); // show that minus overrides the rest
+
+        assertTrue(validator.isValidCountryCodeTld("ch"));
+        DomainValidator.updateTLDOverride(ArrayType.COUNTRY_CODE_MINUS, new String[]{"ch"});
+        assertFalse(validator.isValidCountryCodeTld("ch"));
+        DomainValidator.updateTLDOverride(ArrayType.COUNTRY_CODE_MINUS, new String[]{"xx"});
+        assertTrue(validator.isValidCountryCodeTld("ch"));
+    }
+
+    /**
+     * Test update generic.
+     */
+    @Test
+    public void testUpdateGeneric() {
+        assertFalse(validator.isValidGenericTld("ch")); // cannot be valid
+        DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"ch"});
+        assertTrue(validator.isValidGenericTld("ch")); // it is now!
+        DomainValidator.updateTLDOverride(ArrayType.GENERIC_MINUS, new String[]{"ch"});
+        assertFalse(validator.isValidGenericTld("ch")); // show that minus overrides the rest
+
+        assertTrue(validator.isValidGenericTld("com"));
+        DomainValidator.updateTLDOverride(ArrayType.GENERIC_MINUS, new String[]{"com"});
+        assertFalse(validator.isValidGenericTld("com"));
+        DomainValidator.updateTLDOverride(ArrayType.GENERIC_MINUS, new String[]{"xx"}); // change the minus list
+        assertTrue(validator.isValidGenericTld("com"));
+    }
+
+    /**
+     * Test cannot update.
+     */
+    @Test
+    public void testCannotUpdate() {
+        DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"ch"}); // OK
+        DomainValidator dv = DomainValidator.getInstance();
+        assertNotNull(dv);
+        try {
+            DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"ch"});
+            fail("Expected IllegalStateException");
+        } catch (IllegalStateException ise) {
+            // expected
+            Main.debug(ise.getMessage());
+        }
+    }
+
+    private static boolean isSortedLowerCase(String arrayName) throws Exception {
+        Field f = DomainValidator.class.getDeclaredField(arrayName);
+        final boolean isPrivate = Modifier.isPrivate(f.getModifiers());
+        if (isPrivate) {
+            f.setAccessible(true);
+        }
+        String[] array = (String[]) f.get(null);
+        try {
+            return isSortedLowerCase(arrayName, array);
+        } finally {
+            if (isPrivate) {
+                f.setAccessible(false);
+            }
+        }
+    }
+
+    private static boolean isLowerCase(String string) {
+        return string.equals(string.toLowerCase(Locale.ENGLISH));
+    }
+
+    // Check if an array is strictly sorted - and lowerCase
+    private static boolean isSortedLowerCase(String name, String[] array) {
+        boolean sorted = true;
+        boolean strictlySorted = true;
+        final int length = array.length;
+        boolean lowerCase = isLowerCase(array[length-1]); // Check the last entry
+        for (int i = 0; i < length-1; i++) { // compare all but last entry with next
+            final String entry = array[i];
+            final String nextEntry = array[i+1];
+            final int cmp = entry.compareTo(nextEntry);
+            if (cmp > 0) { // out of order
+                System.out.println("Out of order entry: " + entry + " < " + nextEntry + " in " + name);
+                sorted = false;
+            } else if (cmp == 0) {
+                strictlySorted = false;
+                System.out.println("Duplicated entry: " + entry + " in " + name);
+            }
+            if (!isLowerCase(entry)) {
+                System.out.println("Non lowerCase entry: " + entry + " in " + name);
+                lowerCase = false;
+            }
+        }
+        return sorted && strictlySorted && lowerCase;
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/EmailValidatorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/EmailValidatorTest.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/EmailValidatorTest.java	(revision 9853)
@@ -0,0 +1,576 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Performs Validation Test for e-mail validations.
+ *
+ *
+ * @version $Revision: 1723573 $
+ */
+public class EmailValidatorTest {
+
+    /**
+     * The key used to retrieve the set of validation
+     * rules from the xml file.
+     */
+    protected static String FORM_KEY = "emailForm";
+
+    /**
+     * The key used to retrieve the validator action.
+     */
+    protected static String ACTION = "email";
+
+    private EmailValidator validator;
+
+    /**
+     * Setup
+     */
+    @Before
+    public void setUp() {
+        validator = EmailValidator.getInstance();
+    }
+
+    /**
+     * Tests the e-mail validation.
+     */
+    @Test
+    public void testEmail()  {
+        assertTrue(validator.isValid("jsmith@apache.org"));
+    }
+
+    /**
+     * Tests the email validation with numeric domains.
+     */
+    @Test
+    public void testEmailWithNumericAddress()  {
+        assertTrue(validator.isValid("someone@[216.109.118.76]"));
+        assertTrue(validator.isValid("someone@yahoo.com"));
+    }
+
+    /**
+     * Tests the e-mail validation.
+     */
+    @Test
+    public void testEmailExtension()  {
+        assertTrue(validator.isValid("jsmith@apache.org"));
+
+        assertTrue(validator.isValid("jsmith@apache.com"));
+
+        assertTrue(validator.isValid("jsmith@apache.net"));
+
+        assertTrue(validator.isValid("jsmith@apache.info"));
+
+        assertFalse(validator.isValid("jsmith@apache."));
+
+        assertFalse(validator.isValid("jsmith@apache.c"));
+
+        assertTrue(validator.isValid("someone@yahoo.museum"));
+
+        assertFalse(validator.isValid("someone@yahoo.mu-seum"));
+    }
+
+    /**
+     * Tests the e-mail validation with a dash in
+     * the address.
+     */
+    @Test
+    public void testEmailWithDash()  {
+        assertTrue(validator.isValid("andy.noble@data-workshop.com"));
+
+        assertFalse(validator.isValid("andy-noble@data-workshop.-com"));
+
+        assertFalse(validator.isValid("andy-noble@data-workshop.c-om"));
+
+        assertFalse(validator.isValid("andy-noble@data-workshop.co-m"));
+    }
+
+    /**
+     * Tests the e-mail validation with a dot at the end of
+     * the address.
+     */
+    @Test
+    public void testEmailWithDotEnd()  {
+        assertFalse(validator.isValid("andy.noble@data-workshop.com."));
+    }
+
+    /**
+     * Tests the e-mail validation with an RCS-noncompliant character in
+     * the address.
+     */
+    @Test
+    public void testEmailWithBogusCharacter()  {
+
+        assertFalse(validator.isValid("andy.noble@\u008fdata-workshop.com"));
+
+        // The ' character is valid in an email username.
+        assertTrue(validator.isValid("andy.o'reilly@data-workshop.com"));
+
+        // But not in the domain name.
+        assertFalse(validator.isValid("andy@o'reilly.data-workshop.com"));
+
+        // The + character is valid in an email username.
+        assertTrue(validator.isValid("foo+bar@i.am.not.in.us.example.com"));
+
+        // But not in the domain name
+        assertFalse(validator.isValid("foo+bar@example+3.com"));
+
+        // Domains with only special characters aren't allowed (VALIDATOR-286)
+        assertFalse(validator.isValid("test@%*.com"));
+        assertFalse(validator.isValid("test@^&#.com"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-315
+     */
+    @Test
+    public void testVALIDATOR_315() {
+        assertFalse(validator.isValid("me@at&t.net"));
+        assertTrue(validator.isValid("me@att.net")); // Make sure TLD is not the cause of the failure
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-278
+     */
+    @Test
+    public void testVALIDATOR_278() {
+        assertFalse(validator.isValid("someone@-test.com")); // hostname starts with dash/hyphen
+        assertFalse(validator.isValid("someone@test-.com")); // hostname ends with dash/hyphen
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-235
+     */
+    @Test
+    public void testValidator235() {
+        String version = System.getProperty("java.version");
+        if (version.compareTo("1.6") < 0) {
+            System.out.println("Cannot run Unicode IDN tests");
+            return; // Cannot run the test
+        }
+        assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("someone@xn--d1abbgf6aiiy.xn--p1ai"));
+        assertTrue("президент.рф should validate", validator.isValid("someone@президент.рф"));
+        assertTrue("www.b\u00fccher.ch should validate", validator.isValid("someone@www.b\u00fccher.ch"));
+        assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("someone@www.\uFFFD.ch"));
+        assertTrue("www.b\u00fccher.ch should validate", validator.isValid("someone@www.b\u00fccher.ch"));
+        assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("someone@www.\uFFFD.ch"));
+    }
+
+    /**
+     * Tests the email validation with commas.
+     */
+    @Test
+    public void testEmailWithCommas()  {
+        assertFalse(validator.isValid("joeblow@apa,che.org"));
+
+        assertFalse(validator.isValid("joeblow@apache.o,rg"));
+
+        assertFalse(validator.isValid("joeblow@apache,org"));
+    }
+
+    /**
+     * Tests the email validation with spaces.
+     */
+    @Test
+    public void testEmailWithSpaces()  {
+        assertFalse(validator.isValid("joeblow @apache.org")); // TODO - this should be valid?
+
+        assertFalse(validator.isValid("joeblow@ apache.org"));
+
+        assertTrue(validator.isValid(" joeblow@apache.org")); // TODO - this should be valid?
+
+        assertTrue(validator.isValid("joeblow@apache.org "));
+
+        assertFalse(validator.isValid("joe blow@apache.org "));
+
+        assertFalse(validator.isValid("joeblow@apa che.org "));
+    }
+
+    /**
+     * Tests the email validation with ascii control characters.
+     * (i.e. Ascii chars 0 - 31 and 127)
+     */
+    @Test
+    public void testEmailWithControlChars()  {
+        for (char c = 0; c < 32; c++) {
+            assertFalse("Test control char " + ((int) c), validator.isValid("foo" + c + "bar@domain.com"));
+        }
+        assertFalse("Test control char 127", validator.isValid("foo" + ((char) 127) + "bar@domain.com"));
+    }
+
+    /**
+     * Test that @localhost and @localhost.localdomain
+     *  addresses are declared as valid when requested.
+     */
+    @Test
+    public void testEmailLocalhost() {
+       // Check the default is not to allow
+       EmailValidator noLocal = EmailValidator.getInstance(false);
+       EmailValidator allowLocal = EmailValidator.getInstance(true);
+       assertEquals(validator, noLocal);
+
+       // Depends on the validator
+       assertTrue(
+             "@localhost.localdomain should be accepted but wasn't",
+             allowLocal.isValid("joe@localhost.localdomain")
+       );
+       assertTrue(
+             "@localhost should be accepted but wasn't",
+             allowLocal.isValid("joe@localhost")
+       );
+
+       assertFalse(
+             "@localhost.localdomain should be accepted but wasn't",
+             noLocal.isValid("joe@localhost.localdomain")
+       );
+       assertFalse(
+             "@localhost should be accepted but wasn't",
+             noLocal.isValid("joe@localhost")
+       );
+    }
+
+    /**
+     * VALIDATOR-296 - A / or a ! is valid in the user part,
+     *  but not in the domain part
+     */
+    @Test
+    public void testEmailWithSlashes() {
+       assertTrue(
+             "/ and ! valid in username",
+             validator.isValid("joe!/blow@apache.org")
+       );
+       assertFalse(
+             "/ not valid in domain",
+             validator.isValid("joe@ap/ache.org")
+       );
+       assertFalse(
+             "! not valid in domain",
+             validator.isValid("joe@apac!he.org")
+       );
+    }
+
+    /**
+     * Write this test according to parts of RFC, as opposed to the type of character
+     * that is being tested.
+     */
+    @Test
+    public void testEmailUserName()  {
+
+        assertTrue(validator.isValid("joe1blow@apache.org"));
+
+        assertTrue(validator.isValid("joe$blow@apache.org"));
+
+        assertTrue(validator.isValid("joe-@apache.org"));
+
+        assertTrue(validator.isValid("joe_@apache.org"));
+
+        assertTrue(validator.isValid("joe+@apache.org")); // + is valid unquoted
+
+        assertTrue(validator.isValid("joe!@apache.org")); // ! is valid unquoted
+
+        assertTrue(validator.isValid("joe*@apache.org")); // * is valid unquoted
+
+        assertTrue(validator.isValid("joe'@apache.org")); // ' is valid unquoted
+
+        assertTrue(validator.isValid("joe%45@apache.org")); // % is valid unquoted
+
+        assertTrue(validator.isValid("joe?@apache.org")); // ? is valid unquoted
+
+        assertTrue(validator.isValid("joe&@apache.org")); // & ditto
+
+        assertTrue(validator.isValid("joe=@apache.org")); // = ditto
+
+        assertTrue(validator.isValid("+joe@apache.org")); // + is valid unquoted
+
+        assertTrue(validator.isValid("!joe@apache.org")); // ! is valid unquoted
+
+        assertTrue(validator.isValid("*joe@apache.org")); // * is valid unquoted
+
+        assertTrue(validator.isValid("'joe@apache.org")); // ' is valid unquoted
+
+        assertTrue(validator.isValid("%joe45@apache.org")); // % is valid unquoted
+
+        assertTrue(validator.isValid("?joe@apache.org")); // ? is valid unquoted
+
+        assertTrue(validator.isValid("&joe@apache.org")); // & ditto
+
+        assertTrue(validator.isValid("=joe@apache.org")); // = ditto
+
+        assertTrue(validator.isValid("+@apache.org")); // + is valid unquoted
+
+        assertTrue(validator.isValid("!@apache.org")); // ! is valid unquoted
+
+        assertTrue(validator.isValid("*@apache.org")); // * is valid unquoted
+
+        assertTrue(validator.isValid("'@apache.org")); // ' is valid unquoted
+
+        assertTrue(validator.isValid("%@apache.org")); // % is valid unquoted
+
+        assertTrue(validator.isValid("?@apache.org")); // ? is valid unquoted
+
+        assertTrue(validator.isValid("&@apache.org")); // & ditto
+
+        assertTrue(validator.isValid("=@apache.org")); // = ditto
+
+
+        //UnQuoted Special characters are invalid
+
+        assertFalse(validator.isValid("joe.@apache.org")); // . not allowed at end of local part
+
+        assertFalse(validator.isValid(".joe@apache.org")); // . not allowed at start of local part
+
+        assertFalse(validator.isValid(".@apache.org")); // . not allowed alone
+
+        assertTrue(validator.isValid("joe.ok@apache.org")); // . allowed embedded
+
+        assertFalse(validator.isValid("joe..ok@apache.org")); // .. not allowed embedded
+
+        assertFalse(validator.isValid("..@apache.org")); // .. not allowed alone
+
+        assertFalse(validator.isValid("joe(@apache.org"));
+
+        assertFalse(validator.isValid("joe)@apache.org"));
+
+        assertFalse(validator.isValid("joe,@apache.org"));
+
+        assertFalse(validator.isValid("joe;@apache.org"));
+
+
+        //Quoted Special characters are valid
+        assertTrue(validator.isValid("\"joe.\"@apache.org"));
+
+        assertTrue(validator.isValid("\".joe\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe+\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe!\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe*\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe'\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe(\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe)\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe,\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe%45\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe;\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe?\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe&\"@apache.org"));
+
+        assertTrue(validator.isValid("\"joe=\"@apache.org"));
+
+        assertTrue(validator.isValid("\"..\"@apache.org"));
+
+        // escaped quote character valid in quoted string
+        assertTrue(validator.isValid("\"john\\\"doe\"@apache.org"));
+
+        assertTrue(validator.isValid("john56789.john56789.john56789.john56789.john56789.john56789.john@example.com"));
+
+        assertFalse(validator.isValid("john56789.john56789.john56789.john56789.john56789.john56789.john5@example.com"));
+
+        assertTrue(validator.isValid("\\>escape\\\\special\\^characters\\<@example.com"));
+
+        assertTrue(validator.isValid("Abc\\@def@example.com"));
+
+        assertFalse(validator.isValid("Abc@def@example.com"));
+
+        assertTrue(validator.isValid("space\\ monkey@example.com"));
+    }
+
+    /**
+     * These test values derive directly from RFC 822 &
+     * Mail::RFC822::Address & RFC::RFC822::Address perl test.pl
+     * For traceability don't combine these test values with other tests.
+     */
+    private static final ResultPair[] testEmailFromPerl = {
+        new ResultPair("abigail@example.com", true),
+        new ResultPair("abigail@example.com ", true),
+        new ResultPair(" abigail@example.com", true),
+        new ResultPair("abigail @example.com ", true),
+        new ResultPair("*@example.net", true),
+        new ResultPair("\"\\\"\"@foo.bar", true),
+        new ResultPair("fred&barny@example.com", true),
+        new ResultPair("---@example.com", true),
+        new ResultPair("foo-bar@example.net", true),
+        new ResultPair("\"127.0.0.1\"@[127.0.0.1]", true),
+        new ResultPair("Abigail <abigail@example.com>", true),
+        new ResultPair("Abigail<abigail@example.com>", true),
+        new ResultPair("Abigail<@a,@b,@c:abigail@example.com>", true),
+        new ResultPair("\"This is a phrase\"<abigail@example.com>", true),
+        new ResultPair("\"Abigail \"<abigail@example.com>", true),
+        new ResultPair("\"Joe & J. Harvey\" <example @Org>", true),
+        new ResultPair("Abigail <abigail @ example.com>", true),
+        new ResultPair("Abigail made this <  abigail   @   example  .    com    >", true),
+        new ResultPair("Abigail(the bitch)@example.com", true),
+        new ResultPair("Abigail <abigail @ example . (bar) com >", true),
+        new ResultPair("Abigail < (one)  abigail (two) @(three)example . (bar) com (quz) >", true),
+        new ResultPair("Abigail (foo) (((baz)(nested) (comment)) ! ) < (one)  abigail (two) @(three)example . (bar) com (quz) >", true),
+        new ResultPair("Abigail <abigail(fo\\(o)@example.com>", true),
+        new ResultPair("Abigail <abigail(fo\\)o)@example.com> ", true),
+        new ResultPair("(foo) abigail@example.com", true),
+        new ResultPair("abigail@example.com (foo)", true),
+        new ResultPair("\"Abi\\\"gail\" <abigail@example.com>", true),
+        new ResultPair("abigail@[example.com]", true),
+        new ResultPair("abigail@[exa\\[ple.com]", true),
+        new ResultPair("abigail@[exa\\]ple.com]", true),
+        new ResultPair("\":sysmail\"@  Some-Group. Some-Org", true),
+        new ResultPair("Muhammed.(I am  the greatest) Ali @(the)Vegas.WBA", true),
+        new ResultPair("mailbox.sub1.sub2@this-domain", true),
+        new ResultPair("sub-net.mailbox@sub-domain.domain", true),
+        new ResultPair("name:;", true),
+        new ResultPair("':;", true),
+        new ResultPair("name:   ;", true),
+        new ResultPair("Alfred Neuman <Neuman@BBN-TENEXA>", true),
+        new ResultPair("Neuman@BBN-TENEXA", true),
+        new ResultPair("\"George, Ted\" <Shared@Group.Arpanet>", true),
+        new ResultPair("Wilt . (the  Stilt) Chamberlain@NBA.US", true),
+        new ResultPair("Cruisers:  Port@Portugal, Jones@SEA;", true),
+        new ResultPair("$@[]", true),
+        new ResultPair("*()@[]", true),
+        new ResultPair("\"quoted ( brackets\" ( a comment )@example.com", true),
+        new ResultPair("\"Joe & J. Harvey\"\\x0D\\x0A     <ddd\\@ Org>", true),
+        new ResultPair("\"Joe &\\x0D\\x0A J. Harvey\" <ddd \\@ Org>", true),
+        new ResultPair("Gourmets:  Pompous Person <WhoZiWhatZit\\@Cordon-Bleu>,\\x0D\\x0A" +
+            "        Childs\\@WGBH.Boston, \"Galloping Gourmet\"\\@\\x0D\\x0A" +
+            "        ANT.Down-Under (Australian National Television),\\x0D\\x0A" +
+            "        Cheapie\\@Discount-Liquors;", true),
+        new ResultPair("   Just a string", false),
+        new ResultPair("string", false),
+        new ResultPair("(comment)", false),
+        new ResultPair("()@example.com", false),
+        new ResultPair("fred(&)barny@example.com", false),
+        new ResultPair("fred\\ barny@example.com", false),
+        new ResultPair("Abigail <abi gail @ example.com>", false),
+        new ResultPair("Abigail <abigail(fo(o)@example.com>", false),
+        new ResultPair("Abigail <abigail(fo)o)@example.com>", false),
+        new ResultPair("\"Abi\"gail\" <abigail@example.com>", false),
+        new ResultPair("abigail@[exa]ple.com]", false),
+        new ResultPair("abigail@[exa[ple.com]", false),
+        new ResultPair("abigail@[exaple].com]", false),
+        new ResultPair("abigail@", false),
+        new ResultPair("@example.com", false),
+        new ResultPair("phrase: abigail@example.com abigail@example.com ;", false),
+        new ResultPair("invalid�char@example.com", false)
+    };
+
+    /**
+     * Write this test based on perl Mail::RFC822::Address
+     * which takes its example email address directly from RFC822
+     *
+     * FIXME This test fails so disable it with a leading _ for 1.1.4 release.
+     * The real solution is to fix the email parsing.
+     */
+    @Ignore("This test fails so disable it for 1.1.4 release. The real solution is to fix the email parsing")
+    @Test
+    public void testEmailFromPerl()  {
+        for (int index = 0; index < testEmailFromPerl.length; index++) {
+            String item = testEmailFromPerl[index].item;
+            if (testEmailFromPerl[index].valid) {
+                assertTrue("Should be OK: "+item, validator.isValid(item));
+            } else {
+                assertFalse("Should fail: "+item, validator.isValid(item));
+            }
+        }
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-293
+     */
+    @Test
+    public void testValidator293() {
+        assertTrue(validator.isValid("abc-@abc.com"));
+        assertTrue(validator.isValid("abc_@abc.com"));
+        assertTrue(validator.isValid("abc-def@abc.com"));
+        assertTrue(validator.isValid("abc_def@abc.com"));
+        assertFalse(validator.isValid("abc@abc_def.com"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-365
+     */
+    @Test
+    public void testValidator365() {
+        assertFalse(validator.isValid(
+                "Loremipsumdolorsitametconsecteturadipiscingelit.Nullavitaeligulamattisrhoncusnuncegestasmattisleo."+
+                "Donecnonsapieninmagnatristiquedictumaacturpis.Fusceorciduifacilisisutsapieneuconsequatpharetralectus."+
+                "Quisqueenimestpulvinarutquamvitaeportamattisex.Nullamquismaurisplaceratconvallisjustoquisportamauris."+
+                "Innullalacusconvalliseufringillautvenenatissitametdiam.Maecenasluctusligulascelerisquepulvinarfeugiat."+
+                "Sedmolestienullaaliquetorciluctusidpharetranislfinibus.Suspendissemalesuadatinciduntduisitametportaarcusollicitudinnec."+
+                "Donecetmassamagna.Curabitururnadiampretiumveldignissimporttitorfringillaeuneque."+
+                "Duisantetelluspharetraidtinciduntinterdummolestiesitametfelis.Utquisquamsitametantesagittisdapibusacnonodio."+
+                "Namrutrummolestiediamidmattis.Cumsociisnatoquepenatibusetmagnisdisparturientmontesnasceturridiculusmus."+
+                "Morbiposueresedmetusacconsectetur.Etiamquisipsumvitaejustotempusmaximus.Sedultriciesplaceratvolutpat."+
+                "Integerlacuslectusmaximusacornarequissagittissitametjusto."+
+                "Cumsociisnatoquepenatibusetmagnisdisparturientmontesnasceturridiculusmus.Maecenasindictumpurussedrutrumex.Nullafacilisi."+
+                "Integerfinibusfinibusmietpharetranislfaucibusvel.Maecenasegetdolorlacinialobortisjustovelullamcorpersem."+
+                "Vivamusaliquetpurusidvariusornaresapienrisusrutrumnisitinciduntmollissemnequeidmetus."+
+                "Etiamquiseleifendpurus.Nuncfelisnuncscelerisqueiddignissimnecfinibusalibero."+
+                "Nuncsemperenimnequesitamethendreritpurusfacilisisac.Maurisdapibussemperfelisdignissimgravida."+
+                "Aeneanultricesblanditnequealiquamfinibusodioscelerisqueac.Aliquamnecmassaeumaurisfaucibusfringilla."+
+                "Etiamconsequatligulanisisitametaliquamnibhtemporquis.Nuncinterdumdignissimnullaatsodalesarcusagittiseu."+
+                "Proinpharetrametusneclacuspulvinarsedvolutpatliberoornare.Sedligulanislpulvinarnonlectuseublanditfacilisisante."+
+                "Sedmollisnislalacusauctorsuscipit.Inhachabitasseplateadictumst.Phasellussitametvelittemporvenenatisfeliseuegestasrisus."+
+                "Aliquameteratsitametnibhcommodofinibus.Morbiefficiturodiovelpulvinariaculis."+
+                "Aeneantemporipsummassaaconsecteturturpisfaucibusultrices.Praesentsodalesmaurisquisportafermentum."+
+                "Etiamnisinislvenenatisvelauctorutullamcorperinjusto.Proinvelligulaerat.Phasellusvestibulumgravidamassanonfeugiat."+
+                "Maecenaspharetraeuismodmetusegetefficitur.Suspendisseamet@gmail.com"));
+    }
+
+    /**
+     * Tests the e-mail validation with a user at a TLD
+     *
+     * http://tools.ietf.org/html/rfc5321#section-2.3.5
+     * (In the case of a top-level domain used by itself in an
+     * email address, a single string is used without any dots)
+     */
+    @Test
+    public void testEmailAtTLD() {
+        EmailValidator val = EmailValidator.getInstance(false, true);
+        assertTrue(val.isValid("test@com"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-359
+     */
+    @Test
+    public void testValidator359() {
+        EmailValidator val = EmailValidator.getInstance(false, true);
+        assertFalse(val.isValid("test@.com"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-374
+     */
+    @Test
+    public void testValidator374() {
+        assertTrue(validator.isValid("abc@school.school"));
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/InetAddressValidatorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/InetAddressValidatorTest.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/InetAddressValidatorTest.java	(revision 9853)
@@ -0,0 +1,617 @@
+/*
+ * 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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for InetAddressValidator.
+ *
+ * @version $Revision: 1649163 $
+ */
+public class InetAddressValidatorTest {
+
+    private InetAddressValidator validator;
+
+    /**
+     * Setup
+     */
+    @Before
+    public void setUp() {
+        validator = new InetAddressValidator();
+    }
+
+    /**
+     * Test IPs that point to real, well-known hosts (without actually looking them up).
+     */
+    @Test
+    public void testInetAddressesFromTheWild() {
+        assertTrue("www.apache.org IP should be valid",       validator.isValid("140.211.11.130"));
+        assertTrue("www.l.google.com IP should be valid",     validator.isValid("72.14.253.103"));
+        assertTrue("fsf.org IP should be valid",              validator.isValid("199.232.41.5"));
+        assertTrue("appscs.ign.com IP should be valid",       validator.isValid("216.35.123.87"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-335
+     */
+    @Test
+    public void testVALIDATOR_335() {
+        assertTrue("2001:0438:FFFE:0000:0000:0000:0000:0A35 should be valid",
+                validator.isValid("2001:0438:FFFE:0000:0000:0000:0000:0A35"));
+    }
+
+    /**
+     * Test valid and invalid IPs from each address class.
+     */
+    @Test
+    public void testInetAddressesByClass() {
+        assertTrue("class A IP should be valid",              validator.isValid("24.25.231.12"));
+        assertFalse("illegal class A IP should be invalid",   validator.isValid("2.41.32.324"));
+
+        assertTrue("class B IP should be valid",              validator.isValid("135.14.44.12"));
+        assertFalse("illegal class B IP should be invalid",   validator.isValid("154.123.441.123"));
+
+        assertTrue("class C IP should be valid",              validator.isValid("213.25.224.32"));
+        assertFalse("illegal class C IP should be invalid",   validator.isValid("201.543.23.11"));
+
+        assertTrue("class D IP should be valid",              validator.isValid("229.35.159.6"));
+        assertFalse("illegal class D IP should be invalid",   validator.isValid("231.54.11.987"));
+
+        assertTrue("class E IP should be valid",              validator.isValid("248.85.24.92"));
+        assertFalse("illegal class E IP should be invalid",   validator.isValid("250.21.323.48"));
+    }
+
+    /**
+     * Test reserved IPs.
+     */
+    @Test
+    public void testReservedInetAddresses() {
+        assertTrue("localhost IP should be valid",            validator.isValid("127.0.0.1"));
+        assertTrue("broadcast IP should be valid",            validator.isValid("255.255.255.255"));
+    }
+
+    /**
+     * Test obviously broken IPs.
+     */
+    @Test
+    public void testBrokenInetAddresses() {
+        assertFalse("IP with characters should be invalid",     validator.isValid("124.14.32.abc"));
+        assertFalse("IP with leading zeroes should be invalid", validator.isValid("124.14.32.01"));
+        assertFalse("IP with three groups should be invalid",   validator.isValid("23.64.12"));
+        assertFalse("IP with five groups should be invalid",    validator.isValid("26.34.23.77.234"));
+    }
+
+    // CHECKSTYLE.OFF: LineLength
+    // CHECKSTYLE.OFF: MethodLengthCheck
+    // CHECKSTYLE.OFF: ExecutableStatementCount
+
+    /**
+     * Test IPv6 addresses.
+     * <p>These tests were ported from a
+     * <a href="http://download.dartware.com/thirdparty/test-ipv6-regex.pl">Perl script</a>.</p>
+     */
+    @Test
+    public void testIPv6() {
+        // The original Perl script contained a lot of duplicate tests.
+        // I removed the duplicates I noticed, but there may be more.
+        assertFalse("IPV6 empty string should be invalid", validator.isValidInet6Address("")); // empty string
+        assertTrue("IPV6 ::1 should be valid", validator.isValidInet6Address("::1")); // loopback, compressed, non-routable
+        assertTrue("IPV6 :: should be valid", validator.isValidInet6Address("::")); // unspecified, compressed, non-routable
+        assertTrue("IPV6 0:0:0:0:0:0:0:1 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0:1")); // loopback, full
+        assertTrue("IPV6 0:0:0:0:0:0:0:0 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0:0")); // unspecified, full
+        assertTrue("IPV6 2001:DB8:0:0:8:800:200C:417A should be valid", validator.isValidInet6Address("2001:DB8:0:0:8:800:200C:417A")); // unicast, full
+        assertTrue("IPV6 FF01:0:0:0:0:0:0:101 should be valid", validator.isValidInet6Address("FF01:0:0:0:0:0:0:101")); // multicast, full
+        assertTrue("IPV6 2001:DB8::8:800:200C:417A should be valid", validator.isValidInet6Address("2001:DB8::8:800:200C:417A")); // unicast, compressed
+        assertTrue("IPV6 FF01::101 should be valid", validator.isValidInet6Address("FF01::101")); // multicast, compressed
+        assertFalse("IPV6 2001:DB8:0:0:8:800:200C:417A:221 should be invalid", validator.isValidInet6Address("2001:DB8:0:0:8:800:200C:417A:221")); // unicast, full
+        assertFalse("IPV6 FF01::101::2 should be invalid", validator.isValidInet6Address("FF01::101::2")); // multicast, compressed
+        assertTrue("IPV6 fe80::217:f2ff:fe07:ed62 should be valid", validator.isValidInet6Address("fe80::217:f2ff:fe07:ed62"));
+        assertTrue("IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 should be valid", validator.isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876"));
+        assertTrue("IPV6 3ffe:0b00:0000:0000:0001:0000:0000:000a should be valid", validator.isValidInet6Address("3ffe:0b00:0000:0000:0001:0000:0000:000a"));
+        assertTrue("IPV6 FF02:0000:0000:0000:0000:0000:0000:0001 should be valid", validator.isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0001"));
+        assertTrue("IPV6 0000:0000:0000:0000:0000:0000:0000:0001 should be valid", validator.isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0001"));
+        assertTrue("IPV6 0000:0000:0000:0000:0000:0000:0000:0000 should be valid", validator.isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0000"));
+        assertFalse("IPV6 02001:0000:1234:0000:0000:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("02001:0000:1234:0000:0000:C1C0:ABCD:0876")); // extra 0 not allowed!
+        assertFalse("IPV6 2001:0000:1234:0000:00001:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("2001:0000:1234:0000:00001:C1C0:ABCD:0876")); // extra 0 not allowed!
+        assertFalse("IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0 should be invalid", validator.isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876 0")); // junk after valid address
+        assertFalse("IPV6 2001:0000:1234: 0000:0000:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("2001:0000:1234: 0000:0000:C1C0:ABCD:0876")); // internal space
+        assertFalse("IPV6 3ffe:0b00:0000:0001:0000:0000:000a should be invalid", validator.isValidInet6Address("3ffe:0b00:0000:0001:0000:0000:000a")); // seven segments
+        assertFalse("IPV6 FF02:0000:0000:0000:0000:0000:0000:0000:0001 should be invalid", validator.isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0000:0001")); // nine segments
+        assertFalse("IPV6 3ffe:b00::1::a should be invalid", validator.isValidInet6Address("3ffe:b00::1::a")); // double "::"
+        assertFalse("IPV6 ::1111:2222:3333:4444:5555:6666:: should be invalid", validator.isValidInet6Address("::1111:2222:3333:4444:5555:6666::")); // double "::"
+        assertTrue("IPV6 2::10 should be valid", validator.isValidInet6Address("2::10"));
+        assertTrue("IPV6 ff02::1 should be valid", validator.isValidInet6Address("ff02::1"));
+        assertTrue("IPV6 fe80:: should be valid", validator.isValidInet6Address("fe80::"));
+        assertTrue("IPV6 2002:: should be valid", validator.isValidInet6Address("2002::"));
+        assertTrue("IPV6 2001:db8:: should be valid", validator.isValidInet6Address("2001:db8::"));
+        assertTrue("IPV6 2001:0db8:1234:: should be valid", validator.isValidInet6Address("2001:0db8:1234::"));
+        assertTrue("IPV6 ::ffff:0:0 should be valid", validator.isValidInet6Address("::ffff:0:0"));
+        assertTrue("IPV6 1:2:3:4:5:6:7:8 should be valid", validator.isValidInet6Address("1:2:3:4:5:6:7:8"));
+        assertTrue("IPV6 1:2:3:4:5:6::8 should be valid", validator.isValidInet6Address("1:2:3:4:5:6::8"));
+        assertTrue("IPV6 1:2:3:4:5::8 should be valid", validator.isValidInet6Address("1:2:3:4:5::8"));
+        assertTrue("IPV6 1:2:3:4::8 should be valid", validator.isValidInet6Address("1:2:3:4::8"));
+        assertTrue("IPV6 1:2:3::8 should be valid", validator.isValidInet6Address("1:2:3::8"));
+        assertTrue("IPV6 1:2::8 should be valid", validator.isValidInet6Address("1:2::8"));
+        assertTrue("IPV6 1::8 should be valid", validator.isValidInet6Address("1::8"));
+        assertTrue("IPV6 1::2:3:4:5:6:7 should be valid", validator.isValidInet6Address("1::2:3:4:5:6:7"));
+        assertTrue("IPV6 1::2:3:4:5:6 should be valid", validator.isValidInet6Address("1::2:3:4:5:6"));
+        assertTrue("IPV6 1::2:3:4:5 should be valid", validator.isValidInet6Address("1::2:3:4:5"));
+        assertTrue("IPV6 1::2:3:4 should be valid", validator.isValidInet6Address("1::2:3:4"));
+        assertTrue("IPV6 1::2:3 should be valid", validator.isValidInet6Address("1::2:3"));
+        assertTrue("IPV6 ::2:3:4:5:6:7:8 should be valid", validator.isValidInet6Address("::2:3:4:5:6:7:8"));
+        assertTrue("IPV6 ::2:3:4:5:6:7 should be valid", validator.isValidInet6Address("::2:3:4:5:6:7"));
+        assertTrue("IPV6 ::2:3:4:5:6 should be valid", validator.isValidInet6Address("::2:3:4:5:6"));
+        assertTrue("IPV6 ::2:3:4:5 should be valid", validator.isValidInet6Address("::2:3:4:5"));
+        assertTrue("IPV6 ::2:3:4 should be valid", validator.isValidInet6Address("::2:3:4"));
+        assertTrue("IPV6 ::2:3 should be valid", validator.isValidInet6Address("::2:3"));
+        assertTrue("IPV6 ::8 should be valid", validator.isValidInet6Address("::8"));
+        assertTrue("IPV6 1:2:3:4:5:6:: should be valid", validator.isValidInet6Address("1:2:3:4:5:6::"));
+        assertTrue("IPV6 1:2:3:4:5:: should be valid", validator.isValidInet6Address("1:2:3:4:5::"));
+        assertTrue("IPV6 1:2:3:4:: should be valid", validator.isValidInet6Address("1:2:3:4::"));
+        assertTrue("IPV6 1:2:3:: should be valid", validator.isValidInet6Address("1:2:3::"));
+        assertTrue("IPV6 1:2:: should be valid", validator.isValidInet6Address("1:2::"));
+        assertTrue("IPV6 1:: should be valid", validator.isValidInet6Address("1::"));
+        assertTrue("IPV6 1:2:3:4:5::7:8 should be valid", validator.isValidInet6Address("1:2:3:4:5::7:8"));
+        assertFalse("IPV6 1:2:3::4:5::7:8 should be invalid", validator.isValidInet6Address("1:2:3::4:5::7:8")); // Double "::"
+        assertFalse("IPV6 12345::6:7:8 should be invalid", validator.isValidInet6Address("12345::6:7:8"));
+        assertTrue("IPV6 1:2:3:4::7:8 should be valid", validator.isValidInet6Address("1:2:3:4::7:8"));
+        assertTrue("IPV6 1:2:3::7:8 should be valid", validator.isValidInet6Address("1:2:3::7:8"));
+        assertTrue("IPV6 1:2::7:8 should be valid", validator.isValidInet6Address("1:2::7:8"));
+        assertTrue("IPV6 1::7:8 should be valid", validator.isValidInet6Address("1::7:8"));
+        // IPv4 addresses as dotted-quads
+        assertTrue("IPV6 1:2:3:4:5:6:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4:5:6:1.2.3.4"));
+        assertTrue("IPV6 1:2:3:4:5::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4:5::1.2.3.4"));
+        assertTrue("IPV6 1:2:3:4::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4::1.2.3.4"));
+        assertTrue("IPV6 1:2:3::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3::1.2.3.4"));
+        assertTrue("IPV6 1:2::1.2.3.4 should be valid", validator.isValidInet6Address("1:2::1.2.3.4"));
+        assertTrue("IPV6 1::1.2.3.4 should be valid", validator.isValidInet6Address("1::1.2.3.4"));
+        assertTrue("IPV6 1:2:3:4::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4::5:1.2.3.4"));
+        assertTrue("IPV6 1:2:3::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3::5:1.2.3.4"));
+        assertTrue("IPV6 1:2::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2::5:1.2.3.4"));
+        assertTrue("IPV6 1::5:1.2.3.4 should be valid", validator.isValidInet6Address("1::5:1.2.3.4"));
+        assertTrue("IPV6 1::5:11.22.33.44 should be valid", validator.isValidInet6Address("1::5:11.22.33.44"));
+        assertFalse("IPV6 1::5:400.2.3.4 should be invalid", validator.isValidInet6Address("1::5:400.2.3.4"));
+        assertFalse("IPV6 1::5:260.2.3.4 should be invalid", validator.isValidInet6Address("1::5:260.2.3.4"));
+        assertFalse("IPV6 1::5:256.2.3.4 should be invalid", validator.isValidInet6Address("1::5:256.2.3.4"));
+        assertFalse("IPV6 1::5:1.256.3.4 should be invalid", validator.isValidInet6Address("1::5:1.256.3.4"));
+        assertFalse("IPV6 1::5:1.2.256.4 should be invalid", validator.isValidInet6Address("1::5:1.2.256.4"));
+        assertFalse("IPV6 1::5:1.2.3.256 should be invalid", validator.isValidInet6Address("1::5:1.2.3.256"));
+        assertFalse("IPV6 1::5:300.2.3.4 should be invalid", validator.isValidInet6Address("1::5:300.2.3.4"));
+        assertFalse("IPV6 1::5:1.300.3.4 should be invalid", validator.isValidInet6Address("1::5:1.300.3.4"));
+        assertFalse("IPV6 1::5:1.2.300.4 should be invalid", validator.isValidInet6Address("1::5:1.2.300.4"));
+        assertFalse("IPV6 1::5:1.2.3.300 should be invalid", validator.isValidInet6Address("1::5:1.2.3.300"));
+        assertFalse("IPV6 1::5:900.2.3.4 should be invalid", validator.isValidInet6Address("1::5:900.2.3.4"));
+        assertFalse("IPV6 1::5:1.900.3.4 should be invalid", validator.isValidInet6Address("1::5:1.900.3.4"));
+        assertFalse("IPV6 1::5:1.2.900.4 should be invalid", validator.isValidInet6Address("1::5:1.2.900.4"));
+        assertFalse("IPV6 1::5:1.2.3.900 should be invalid", validator.isValidInet6Address("1::5:1.2.3.900"));
+        assertFalse("IPV6 1::5:300.300.300.300 should be invalid", validator.isValidInet6Address("1::5:300.300.300.300"));
+        assertFalse("IPV6 1::5:3000.30.30.30 should be invalid", validator.isValidInet6Address("1::5:3000.30.30.30"));
+        assertFalse("IPV6 1::400.2.3.4 should be invalid", validator.isValidInet6Address("1::400.2.3.4"));
+        assertFalse("IPV6 1::260.2.3.4 should be invalid", validator.isValidInet6Address("1::260.2.3.4"));
+        assertFalse("IPV6 1::256.2.3.4 should be invalid", validator.isValidInet6Address("1::256.2.3.4"));
+        assertFalse("IPV6 1::1.256.3.4 should be invalid", validator.isValidInet6Address("1::1.256.3.4"));
+        assertFalse("IPV6 1::1.2.256.4 should be invalid", validator.isValidInet6Address("1::1.2.256.4"));
+        assertFalse("IPV6 1::1.2.3.256 should be invalid", validator.isValidInet6Address("1::1.2.3.256"));
+        assertFalse("IPV6 1::300.2.3.4 should be invalid", validator.isValidInet6Address("1::300.2.3.4"));
+        assertFalse("IPV6 1::1.300.3.4 should be invalid", validator.isValidInet6Address("1::1.300.3.4"));
+        assertFalse("IPV6 1::1.2.300.4 should be invalid", validator.isValidInet6Address("1::1.2.300.4"));
+        assertFalse("IPV6 1::1.2.3.300 should be invalid", validator.isValidInet6Address("1::1.2.3.300"));
+        assertFalse("IPV6 1::900.2.3.4 should be invalid", validator.isValidInet6Address("1::900.2.3.4"));
+        assertFalse("IPV6 1::1.900.3.4 should be invalid", validator.isValidInet6Address("1::1.900.3.4"));
+        assertFalse("IPV6 1::1.2.900.4 should be invalid", validator.isValidInet6Address("1::1.2.900.4"));
+        assertFalse("IPV6 1::1.2.3.900 should be invalid", validator.isValidInet6Address("1::1.2.3.900"));
+        assertFalse("IPV6 1::300.300.300.300 should be invalid", validator.isValidInet6Address("1::300.300.300.300"));
+        assertFalse("IPV6 1::3000.30.30.30 should be invalid", validator.isValidInet6Address("1::3000.30.30.30"));
+        assertFalse("IPV6 ::400.2.3.4 should be invalid", validator.isValidInet6Address("::400.2.3.4"));
+        assertFalse("IPV6 ::260.2.3.4 should be invalid", validator.isValidInet6Address("::260.2.3.4"));
+        assertFalse("IPV6 ::256.2.3.4 should be invalid", validator.isValidInet6Address("::256.2.3.4"));
+        assertFalse("IPV6 ::1.256.3.4 should be invalid", validator.isValidInet6Address("::1.256.3.4"));
+        assertFalse("IPV6 ::1.2.256.4 should be invalid", validator.isValidInet6Address("::1.2.256.4"));
+        assertFalse("IPV6 ::1.2.3.256 should be invalid", validator.isValidInet6Address("::1.2.3.256"));
+        assertFalse("IPV6 ::300.2.3.4 should be invalid", validator.isValidInet6Address("::300.2.3.4"));
+        assertFalse("IPV6 ::1.300.3.4 should be invalid", validator.isValidInet6Address("::1.300.3.4"));
+        assertFalse("IPV6 ::1.2.300.4 should be invalid", validator.isValidInet6Address("::1.2.300.4"));
+        assertFalse("IPV6 ::1.2.3.300 should be invalid", validator.isValidInet6Address("::1.2.3.300"));
+        assertFalse("IPV6 ::900.2.3.4 should be invalid", validator.isValidInet6Address("::900.2.3.4"));
+        assertFalse("IPV6 ::1.900.3.4 should be invalid", validator.isValidInet6Address("::1.900.3.4"));
+        assertFalse("IPV6 ::1.2.900.4 should be invalid", validator.isValidInet6Address("::1.2.900.4"));
+        assertFalse("IPV6 ::1.2.3.900 should be invalid", validator.isValidInet6Address("::1.2.3.900"));
+        assertFalse("IPV6 ::300.300.300.300 should be invalid", validator.isValidInet6Address("::300.300.300.300"));
+        assertFalse("IPV6 ::3000.30.30.30 should be invalid", validator.isValidInet6Address("::3000.30.30.30"));
+        assertTrue("IPV6 fe80::217:f2ff:254.7.237.98 should be valid", validator.isValidInet6Address("fe80::217:f2ff:254.7.237.98"));
+        assertTrue("IPV6 ::ffff:192.168.1.26 should be valid", validator.isValidInet6Address("::ffff:192.168.1.26"));
+        assertFalse("IPV6 2001:1:1:1:1:1:255Z255X255Y255 should be invalid", validator.isValidInet6Address("2001:1:1:1:1:1:255Z255X255Y255")); // garbage instead of "." in IPv4
+        assertFalse("IPV6 ::ffff:192x168.1.26 should be invalid", validator.isValidInet6Address("::ffff:192x168.1.26")); // ditto
+        assertTrue("IPV6 ::ffff:192.168.1.1 should be valid", validator.isValidInet6Address("::ffff:192.168.1.1"));
+        assertTrue("IPV6 0:0:0:0:0:0:13.1.68.3 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:13.1.68.3")); // IPv4-compatible IPv6 address, full, deprecated
+        assertTrue("IPV6 0:0:0:0:0:FFFF:129.144.52.38 should be valid", validator.isValidInet6Address("0:0:0:0:0:FFFF:129.144.52.38")); // IPv4-mapped IPv6 address, full
+        assertTrue("IPV6 ::13.1.68.3 should be valid", validator.isValidInet6Address("::13.1.68.3")); // IPv4-compatible IPv6 address, compressed, deprecated
+        assertTrue("IPV6 ::FFFF:129.144.52.38 should be valid", validator.isValidInet6Address("::FFFF:129.144.52.38")); // IPv4-mapped IPv6 address, compressed
+        assertTrue("IPV6 fe80:0:0:0:204:61ff:254.157.241.86 should be valid", validator.isValidInet6Address("fe80:0:0:0:204:61ff:254.157.241.86"));
+        assertTrue("IPV6 fe80::204:61ff:254.157.241.86 should be valid", validator.isValidInet6Address("fe80::204:61ff:254.157.241.86"));
+        assertTrue("IPV6 ::ffff:12.34.56.78 should be valid", validator.isValidInet6Address("::ffff:12.34.56.78"));
+        assertFalse("IPV6 ::ffff:2.3.4 should be invalid", validator.isValidInet6Address("::ffff:2.3.4"));
+        assertFalse("IPV6 ::ffff:257.1.2.3 should be invalid", validator.isValidInet6Address("::ffff:257.1.2.3"));
+        assertFalse("IPV6 1.2.3.4 should be invalid", validator.isValidInet6Address("1.2.3.4"));
+        assertFalse("IPV6 1.2.3.4:1111:2222:3333:4444::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222:3333:4444::5555"));
+        assertFalse("IPV6 1.2.3.4:1111:2222:3333::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222:3333::5555"));
+        assertFalse("IPV6 1.2.3.4:1111:2222::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222::5555"));
+        assertFalse("IPV6 1.2.3.4:1111::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111::5555"));
+        assertFalse("IPV6 1.2.3.4::5555 should be invalid", validator.isValidInet6Address("1.2.3.4::5555"));
+        assertFalse("IPV6 1.2.3.4:: should be invalid", validator.isValidInet6Address("1.2.3.4::"));
+        // Testing IPv4 addresses represented as dotted-quads
+        // Leading zeroes in IPv4 addresses not allowed: some systems treat the leading "0" in ".086" as the start of an octal number
+        // Update: The BNF in RFC-3986 explicitly defines the dec-octet (for IPv4 addresses) not to have a leading zero
+        assertFalse("IPV6 fe80:0000:0000:0000:0204:61ff:254.157.241.086 should be invalid", validator.isValidInet6Address("fe80:0000:0000:0000:0204:61ff:254.157.241.086"));
+        assertTrue("IPV6 ::ffff:192.0.2.128 should be valid", validator.isValidInet6Address("::ffff:192.0.2.128")); // but this is OK, since there's a single digit
+        assertFalse("IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4 should be invalid", validator.isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:00.00.00.00 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:00.00.00.00"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:000.000.000.000 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:000.000.000.000"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:256.256.256.256 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:256.256.256.256"));
+        assertTrue("IPV6 fe80:0000:0000:0000:0204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80:0000:0000:0000:0204:61ff:fe9d:f156"));
+        assertTrue("IPV6 fe80:0:0:0:204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80:0:0:0:204:61ff:fe9d:f156"));
+        assertTrue("IPV6 fe80::204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80::204:61ff:fe9d:f156"));
+        assertFalse("IPV6 : should be invalid", validator.isValidInet6Address(":"));
+        assertTrue("IPV6 ::ffff:c000:280 should be valid", validator.isValidInet6Address("::ffff:c000:280"));
+        assertFalse("IPV6 1111:2222:3333:4444::5555: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::5555:"));
+        assertFalse("IPV6 1111:2222:3333::5555: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:"));
+        assertFalse("IPV6 1111:2222::5555: should be invalid", validator.isValidInet6Address("1111:2222::5555:"));
+        assertFalse("IPV6 1111::5555: should be invalid", validator.isValidInet6Address("1111::5555:"));
+        assertFalse("IPV6 ::5555: should be invalid", validator.isValidInet6Address("::5555:"));
+        assertFalse("IPV6 ::: should be invalid", validator.isValidInet6Address(":::"));
+        assertFalse("IPV6 1111: should be invalid", validator.isValidInet6Address("1111:"));
+        assertFalse("IPV6 :1111:2222:3333:4444::5555 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::5555"));
+        assertFalse("IPV6 :1111:2222:3333::5555 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555"));
+        assertFalse("IPV6 :1111:2222::5555 should be invalid", validator.isValidInet6Address(":1111:2222::5555"));
+        assertFalse("IPV6 :1111::5555 should be invalid", validator.isValidInet6Address(":1111::5555"));
+        assertFalse("IPV6 :::5555 should be invalid", validator.isValidInet6Address(":::5555"));
+        assertTrue("IPV6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 should be valid", validator.isValidInet6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
+        assertTrue("IPV6 2001:db8:85a3:0:0:8a2e:370:7334 should be valid", validator.isValidInet6Address("2001:db8:85a3:0:0:8a2e:370:7334"));
+        assertTrue("IPV6 2001:db8:85a3::8a2e:370:7334 should be valid", validator.isValidInet6Address("2001:db8:85a3::8a2e:370:7334"));
+        assertTrue("IPV6 2001:0db8:0000:0000:0000:0000:1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0000:0000:0000:0000:1428:57ab"));
+        assertTrue("IPV6 2001:0db8:0000:0000:0000::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0000:0000:0000::1428:57ab"));
+        assertTrue("IPV6 2001:0db8:0:0:0:0:1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0:0:0:0:1428:57ab"));
+        assertTrue("IPV6 2001:0db8:0:0::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0:0::1428:57ab"));
+        assertTrue("IPV6 2001:0db8::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8::1428:57ab"));
+        assertTrue("IPV6 2001:db8::1428:57ab should be valid", validator.isValidInet6Address("2001:db8::1428:57ab"));
+        assertTrue("IPV6 ::ffff:0c22:384e should be valid", validator.isValidInet6Address("::ffff:0c22:384e"));
+        assertTrue("IPV6 2001:0db8:1234:0000:0000:0000:0000:0000 should be valid", validator.isValidInet6Address("2001:0db8:1234:0000:0000:0000:0000:0000"));
+        assertTrue("IPV6 2001:0db8:1234:ffff:ffff:ffff:ffff:ffff should be valid", validator.isValidInet6Address("2001:0db8:1234:ffff:ffff:ffff:ffff:ffff"));
+        assertTrue("IPV6 2001:db8:a::123 should be valid", validator.isValidInet6Address("2001:db8:a::123"));
+        assertFalse("IPV6 123 should be invalid", validator.isValidInet6Address("123"));
+        assertFalse("IPV6 ldkfj should be invalid", validator.isValidInet6Address("ldkfj"));
+        assertFalse("IPV6 2001::FFD3::57ab should be invalid", validator.isValidInet6Address("2001::FFD3::57ab"));
+        assertFalse("IPV6 2001:db8:85a3::8a2e:37023:7334 should be invalid", validator.isValidInet6Address("2001:db8:85a3::8a2e:37023:7334"));
+        assertFalse("IPV6 2001:db8:85a3::8a2e:370k:7334 should be invalid", validator.isValidInet6Address("2001:db8:85a3::8a2e:370k:7334"));
+        assertFalse("IPV6 1:2:3:4:5:6:7:8:9 should be invalid", validator.isValidInet6Address("1:2:3:4:5:6:7:8:9"));
+        assertFalse("IPV6 1::2::3 should be invalid", validator.isValidInet6Address("1::2::3"));
+        assertFalse("IPV6 1:::3:4:5 should be invalid", validator.isValidInet6Address("1:::3:4:5"));
+        assertFalse("IPV6 1:2:3::4:5:6:7:8:9 should be invalid", validator.isValidInet6Address("1:2:3::4:5:6:7:8:9"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555:6666:7777:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777::"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555:6666:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::"));
+        assertTrue("IPV6 1111:2222:3333:4444:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444::"));
+        assertTrue("IPV6 1111:2222:3333:: should be valid", validator.isValidInet6Address("1111:2222:3333::"));
+        assertTrue("IPV6 1111:2222:: should be valid", validator.isValidInet6Address("1111:2222::"));
+        assertTrue("IPV6 1111:: should be valid", validator.isValidInet6Address("1111::"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555:6666::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::8888"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::8888"));
+        assertTrue("IPV6 1111:2222:3333:4444::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::8888"));
+        assertTrue("IPV6 1111:2222:3333::8888 should be valid", validator.isValidInet6Address("1111:2222:3333::8888"));
+        assertTrue("IPV6 1111:2222::8888 should be valid", validator.isValidInet6Address("1111:2222::8888"));
+        assertTrue("IPV6 1111::8888 should be valid", validator.isValidInet6Address("1111::8888"));
+        assertTrue("IPV6 ::8888 should be valid", validator.isValidInet6Address("::8888"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777:8888"));
+        assertTrue("IPV6 1111:2222:3333:4444::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::7777:8888"));
+        assertTrue("IPV6 1111:2222:3333::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::7777:8888"));
+        assertTrue("IPV6 1111:2222::7777:8888 should be valid", validator.isValidInet6Address("1111:2222::7777:8888"));
+        assertTrue("IPV6 1111::7777:8888 should be valid", validator.isValidInet6Address("1111::7777:8888"));
+        assertTrue("IPV6 ::7777:8888 should be valid", validator.isValidInet6Address("::7777:8888"));
+        assertTrue("IPV6 1111:2222:3333:4444::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777:8888"));
+        assertTrue("IPV6 1111:2222:3333::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::6666:7777:8888"));
+        assertTrue("IPV6 1111:2222::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::6666:7777:8888"));
+        assertTrue("IPV6 1111::6666:7777:8888 should be valid", validator.isValidInet6Address("1111::6666:7777:8888"));
+        assertTrue("IPV6 ::6666:7777:8888 should be valid", validator.isValidInet6Address("::6666:7777:8888"));
+        assertTrue("IPV6 1111:2222:3333::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777:8888"));
+        assertTrue("IPV6 1111:2222::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::5555:6666:7777:8888"));
+        assertTrue("IPV6 1111::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::5555:6666:7777:8888"));
+        assertTrue("IPV6 ::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::5555:6666:7777:8888"));
+        assertTrue("IPV6 1111:2222::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 1111::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 ::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 1111::3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 ::3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::3333:4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 ::2222:3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:123.123.123.123"));
+        assertTrue("IPV6 1111:2222:3333:4444:5555::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::123.123.123.123"));
+        assertTrue("IPV6 1111:2222:3333:4444::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::123.123.123.123"));
+        assertTrue("IPV6 1111:2222:3333::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::123.123.123.123"));
+        assertTrue("IPV6 1111:2222::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::123.123.123.123"));
+        assertTrue("IPV6 1111::123.123.123.123 should be valid", validator.isValidInet6Address("1111::123.123.123.123"));
+        assertTrue("IPV6 ::123.123.123.123 should be valid", validator.isValidInet6Address("::123.123.123.123"));
+        assertTrue("IPV6 1111:2222:3333:4444::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::6666:123.123.123.123"));
+        assertTrue("IPV6 1111:2222:3333::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::6666:123.123.123.123"));
+        assertTrue("IPV6 1111:2222::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::6666:123.123.123.123"));
+        assertTrue("IPV6 1111::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::6666:123.123.123.123"));
+        assertTrue("IPV6 ::6666:123.123.123.123 should be valid", validator.isValidInet6Address("::6666:123.123.123.123"));
+        assertTrue("IPV6 1111:2222:3333::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::5555:6666:123.123.123.123"));
+        assertTrue("IPV6 1111:2222::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::5555:6666:123.123.123.123"));
+        assertTrue("IPV6 1111::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::5555:6666:123.123.123.123"));
+        assertTrue("IPV6 ::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::5555:6666:123.123.123.123"));
+        assertTrue("IPV6 1111:2222::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::4444:5555:6666:123.123.123.123"));
+        assertTrue("IPV6 1111::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::4444:5555:6666:123.123.123.123"));
+        assertTrue("IPV6 ::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::4444:5555:6666:123.123.123.123"));
+        assertTrue("IPV6 1111::3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::3333:4444:5555:6666:123.123.123.123"));
+        assertTrue("IPV6 ::2222:3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:123.123.123.123"));
+        // Trying combinations of "0" and "::"
+        // These are all syntactically correct, but are bad form
+        // because "0" adjacent to "::" should be combined into "::"
+        assertTrue("IPV6 ::0:0:0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0:0:0"));
+        assertTrue("IPV6 ::0:0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0:0"));
+        assertTrue("IPV6 ::0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0"));
+        assertTrue("IPV6 ::0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0"));
+        assertTrue("IPV6 ::0:0:0 should be valid", validator.isValidInet6Address("::0:0:0"));
+        assertTrue("IPV6 ::0:0 should be valid", validator.isValidInet6Address("::0:0"));
+        assertTrue("IPV6 ::0 should be valid", validator.isValidInet6Address("::0"));
+        assertTrue("IPV6 0:0:0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0::"));
+        assertTrue("IPV6 0:0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0:0::"));
+        assertTrue("IPV6 0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0::"));
+        assertTrue("IPV6 0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0::"));
+        assertTrue("IPV6 0:0:0:: should be valid", validator.isValidInet6Address("0:0:0::"));
+        assertTrue("IPV6 0:0:: should be valid", validator.isValidInet6Address("0:0::"));
+        assertTrue("IPV6 0:: should be valid", validator.isValidInet6Address("0::"));
+        // Invalid data
+        assertFalse("IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX should be invalid", validator.isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX"));
+        // Too many components
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:9999 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:9999"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888::"));
+        assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:8888:9999 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888:9999"));
+        // Too few components
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555"));
+        assertFalse("IPV6 1111:2222:3333:4444 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444"));
+        assertFalse("IPV6 1111:2222:3333 should be invalid", validator.isValidInet6Address("1111:2222:3333"));
+        assertFalse("IPV6 1111:2222 should be invalid", validator.isValidInet6Address("1111:2222"));
+        assertFalse("IPV6 1111 should be invalid", validator.isValidInet6Address("1111"));
+        // Missing :
+        assertFalse("IPV6 11112222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("11112222:3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:22223333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:22223333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:33334444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:33334444:5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:44445555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:44445555:6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:55556666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:55556666:7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:66667777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:66667777:8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:77778888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:77778888"));
+        // Missing : intended for ::
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:"));
+        assertFalse("IPV6 1111:2222:3333:4444: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:"));
+        assertFalse("IPV6 1111:2222:3333: should be invalid", validator.isValidInet6Address("1111:2222:3333:"));
+        assertFalse("IPV6 1111:2222: should be invalid", validator.isValidInet6Address("1111:2222:"));
+        assertFalse("IPV6 :8888 should be invalid", validator.isValidInet6Address(":8888"));
+        assertFalse("IPV6 :7777:8888 should be invalid", validator.isValidInet6Address(":7777:8888"));
+        assertFalse("IPV6 :6666:7777:8888 should be invalid", validator.isValidInet6Address(":6666:7777:8888"));
+        assertFalse("IPV6 :5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":5555:6666:7777:8888"));
+        assertFalse("IPV6 :4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":2222:3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:7777:8888"));
+        // :::
+        assertFalse("IPV6 :::2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:::3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:::4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:::5555:6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::6666:7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::7777:8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:::8888"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:::"));
+        // Double ::
+        assertFalse("IPV6 ::2222::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222::4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 ::2222:3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333::5555:6666:7777:8888"));
+        assertFalse("IPV6 ::2222:3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444::6666:7777:8888"));
+        assertFalse("IPV6 ::2222:3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555::7777:8888"));
+        assertFalse("IPV6 ::2222:3333:4444:5555:7777::8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:7777::8888"));
+        assertFalse("IPV6 ::2222:3333:4444:5555:7777:8888:: should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:7777:8888::"));
+        assertFalse("IPV6 1111::3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111::3333::5555:6666:7777:8888"));
+        assertFalse("IPV6 1111::3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111::3333:4444::6666:7777:8888"));
+        assertFalse("IPV6 1111::3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555::7777:8888"));
+        assertFalse("IPV6 1111::3333:4444:5555:6666::8888 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666::8888"));
+        assertFalse("IPV6 1111::3333:4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777::"));
+        assertFalse("IPV6 1111:2222::4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222::4444::6666:7777:8888"));
+        assertFalse("IPV6 1111:2222::4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555::7777:8888"));
+        assertFalse("IPV6 1111:2222::4444:5555:6666::8888 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666::8888"));
+        assertFalse("IPV6 1111:2222::4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777::"));
+        assertFalse("IPV6 1111:2222:3333::5555::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555::7777:8888"));
+        assertFalse("IPV6 1111:2222:3333::5555:6666::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666::8888"));
+        assertFalse("IPV6 1111:2222:3333::5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777::"));
+        assertFalse("IPV6 1111:2222:3333:4444::6666::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666::8888"));
+        assertFalse("IPV6 1111:2222:3333:4444::6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777::"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555::7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777::"));
+        // Too many components"
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::1.2.3.4"));
+        assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:1.2.3.4.5 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:1.2.3.4.5"));
+        // Too few components
+        assertFalse("IPV6 1111:2222:3333:4444:5555:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:1.2.3.4"));
+        assertFalse("IPV6 1111:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:1.2.3.4"));
+        assertFalse("IPV6 1.2.3.4 should be invalid", validator.isValidInet6Address("1.2.3.4"));
+        // Missing :
+        assertFalse("IPV6 11112222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("11112222:3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:22223333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:22223333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:33334444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:33334444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:44445555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:44445555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:55556666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:55556666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:66661.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:66661.2.3.4"));
+        // Missing .
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255255.255.255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255255.255.255"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255.255255.255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255.255255.255"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255.255.255255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255.255.255255"));
+        // Missing : intended for ::
+        assertFalse("IPV6 :1.2.3.4 should be invalid", validator.isValidInet6Address(":1.2.3.4"));
+        assertFalse("IPV6 :6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":6666:1.2.3.4"));
+        assertFalse("IPV6 :5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":2222:3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:1.2.3.4"));
+        // :::
+        assertFalse("IPV6 :::2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:::3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:::3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:::4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::1.2.3.4"));
+        // Double ::
+        assertFalse("IPV6 ::2222::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222::4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 ::2222:3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 ::2222:3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444::6666:1.2.3.4"));
+        assertFalse("IPV6 ::2222:3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555::1.2.3.4"));
+        assertFalse("IPV6 1111::3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 1111::3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333:4444::6666:1.2.3.4"));
+        assertFalse("IPV6 1111::3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555::1.2.3.4"));
+        assertFalse("IPV6 1111:2222::4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222::4444::6666:1.2.3.4"));
+        assertFalse("IPV6 1111:2222::4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555::1.2.3.4"));
+        assertFalse("IPV6 1111:2222:3333::5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555::1.2.3.4"));
+        // Missing parts
+        assertFalse("IPV6 ::. should be invalid", validator.isValidInet6Address("::."));
+        assertFalse("IPV6 ::.. should be invalid", validator.isValidInet6Address("::.."));
+        assertFalse("IPV6 ::... should be invalid", validator.isValidInet6Address("::..."));
+        assertFalse("IPV6 ::1... should be invalid", validator.isValidInet6Address("::1..."));
+        assertFalse("IPV6 ::1.2.. should be invalid", validator.isValidInet6Address("::1.2.."));
+        assertFalse("IPV6 ::1.2.3. should be invalid", validator.isValidInet6Address("::1.2.3."));
+        assertFalse("IPV6 ::.2.. should be invalid", validator.isValidInet6Address("::.2.."));
+        assertFalse("IPV6 ::.2.3. should be invalid", validator.isValidInet6Address("::.2.3."));
+        assertFalse("IPV6 ::.2.3.4 should be invalid", validator.isValidInet6Address("::.2.3.4"));
+        assertFalse("IPV6 ::..3. should be invalid", validator.isValidInet6Address("::..3."));
+        assertFalse("IPV6 ::..3.4 should be invalid", validator.isValidInet6Address("::..3.4"));
+        assertFalse("IPV6 ::...4 should be invalid", validator.isValidInet6Address("::...4"));
+        // Extra : in front
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:7777::"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666::"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::"));
+        assertFalse("IPV6 :1111:2222:3333:4444:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::"));
+        assertFalse("IPV6 :1111:2222:3333:: should be invalid", validator.isValidInet6Address(":1111:2222:3333::"));
+        assertFalse("IPV6 :1111:2222:: should be invalid", validator.isValidInet6Address(":1111:2222::"));
+        assertFalse("IPV6 :1111:: should be invalid", validator.isValidInet6Address(":1111::"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:6666::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666::8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::8888"));
+        assertFalse("IPV6 :1111:2222:3333::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::8888"));
+        assertFalse("IPV6 :1111:2222::8888 should be invalid", validator.isValidInet6Address(":1111:2222::8888"));
+        assertFalse("IPV6 :1111::8888 should be invalid", validator.isValidInet6Address(":1111::8888"));
+        assertFalse("IPV6 :::8888 should be invalid", validator.isValidInet6Address(":::8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::7777:8888"));
+        assertFalse("IPV6 :1111:2222::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::7777:8888"));
+        assertFalse("IPV6 :1111::7777:8888 should be invalid", validator.isValidInet6Address(":1111::7777:8888"));
+        assertFalse("IPV6 :::7777:8888 should be invalid", validator.isValidInet6Address(":::7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::6666:7777:8888"));
+        assertFalse("IPV6 :1111::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::6666:7777:8888"));
+        assertFalse("IPV6 :::6666:7777:8888 should be invalid", validator.isValidInet6Address(":::6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::5555:6666:7777:8888"));
+        assertFalse("IPV6 :::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :::2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:7777:8888"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333:4444::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::1.2.3.4"));
+        assertFalse("IPV6 :1111:2222::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::1.2.3.4"));
+        assertFalse("IPV6 :1111::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::1.2.3.4"));
+        assertFalse("IPV6 :::1.2.3.4 should be invalid", validator.isValidInet6Address(":::1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::6666:1.2.3.4"));
+        assertFalse("IPV6 :1111::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::6666:1.2.3.4"));
+        assertFalse("IPV6 :::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222:3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111:2222::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :1111::3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::3333:4444:5555:6666:1.2.3.4"));
+        assertFalse("IPV6 :::2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:1.2.3.4"));
+        // Extra : at end
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:::"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:::"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::"));
+        assertFalse("IPV6 1111:2222:3333:4444::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::"));
+        assertFalse("IPV6 1111:2222:3333::: should be invalid", validator.isValidInet6Address("1111:2222:3333:::"));
+        assertFalse("IPV6 1111:2222::: should be invalid", validator.isValidInet6Address("1111:2222:::"));
+        assertFalse("IPV6 1111::: should be invalid", validator.isValidInet6Address("1111:::"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555:6666::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::8888:"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::8888:"));
+        assertFalse("IPV6 1111:2222:3333:4444::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::8888:"));
+        assertFalse("IPV6 1111:2222:3333::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::8888:"));
+        assertFalse("IPV6 1111:2222::8888: should be invalid", validator.isValidInet6Address("1111:2222::8888:"));
+        assertFalse("IPV6 1111::8888: should be invalid", validator.isValidInet6Address("1111::8888:"));
+        assertFalse("IPV6 ::8888: should be invalid", validator.isValidInet6Address("::8888:"));
+        assertFalse("IPV6 1111:2222:3333:4444:5555::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777:8888:"));
+        assertFalse("IPV6 1111:2222:3333:4444::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::7777:8888:"));
+        assertFalse("IPV6 1111:2222:3333::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::7777:8888:"));
+        assertFalse("IPV6 1111:2222::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::7777:8888:"));
+        assertFalse("IPV6 1111::7777:8888: should be invalid", validator.isValidInet6Address("1111::7777:8888:"));
+        assertFalse("IPV6 ::7777:8888: should be invalid", validator.isValidInet6Address("::7777:8888:"));
+        assertFalse("IPV6 1111:2222:3333:4444::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777:8888:"));
+        assertFalse("IPV6 1111:2222:3333::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::6666:7777:8888:"));
+        assertFalse("IPV6 1111:2222::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::6666:7777:8888:"));
+        assertFalse("IPV6 1111::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::6666:7777:8888:"));
+        assertFalse("IPV6 ::6666:7777:8888: should be invalid", validator.isValidInet6Address("::6666:7777:8888:"));
+        assertFalse("IPV6 1111:2222:3333::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777:8888:"));
+        assertFalse("IPV6 1111:2222::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::5555:6666:7777:8888:"));
+        assertFalse("IPV6 1111::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::5555:6666:7777:8888:"));
+        assertFalse("IPV6 ::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::5555:6666:7777:8888:"));
+        assertFalse("IPV6 1111:2222::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777:8888:"));
+        assertFalse("IPV6 1111::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::4444:5555:6666:7777:8888:"));
+        assertFalse("IPV6 ::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::4444:5555:6666:7777:8888:"));
+        assertFalse("IPV6 1111::3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777:8888:"));
+        assertFalse("IPV6 ::3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::3333:4444:5555:6666:7777:8888:"));
+        assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888:"));
+        assertTrue("IPV6 0:a:b:c:d:e:f:: should be valid", validator.isValidInet6Address("0:a:b:c:d:e:f::"));
+        assertTrue("IPV6 ::0:a:b:c:d:e:f should be valid", validator.isValidInet6Address("::0:a:b:c:d:e:f")); // syntactically correct, but bad form (::0:... could be combined)
+        assertTrue("IPV6 a:b:c:d:e:f:0:: should be valid", validator.isValidInet6Address("a:b:c:d:e:f:0::"));
+        assertFalse("IPV6 ':10.0.0.1 should be invalid", validator.isValidInet6Address("':10.0.0.1"));
+    }
+    // CHECKSTYLE.ON: ExecutableStatementCount
+    // CHECKSTYLE.ON: MethodLengthCheck
+    // CHECKSTYLE.ON: LineLength
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/RegexValidatorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/RegexValidatorTest.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/RegexValidatorTest.java	(revision 9853)
@@ -0,0 +1,285 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.regex.PatternSyntaxException;
+
+import org.junit.Test;
+import org.openstreetmap.josm.Main;
+
+/**
+ * Test Case for RegexValidatorTest.
+ *
+ * @version $Revision: 1649191 $
+ * @since Validator 1.4
+ */
+public class RegexValidatorTest {
+
+    private static final String REGEX         = "^([abc]*)(?:\\-)([DEF]*)(?:\\-)([123]*)$";
+
+    private static final String COMPONENT_1 = "([abc]{3})";
+    private static final String COMPONENT_2 = "([DEF]{3})";
+    private static final String COMPONENT_3 = "([123]{3})";
+    private static final String SEPARATOR_1  = "(?:\\-)";
+    private static final String SEPARATOR_2  = "(?:\\s)";
+    private static final String REGEX_1 = "^" + COMPONENT_1 + SEPARATOR_1 + COMPONENT_2 + SEPARATOR_1 + COMPONENT_3 + "$";
+    private static final String REGEX_2 = "^" + COMPONENT_1 + SEPARATOR_2 + COMPONENT_2 + SEPARATOR_2 + COMPONENT_3 + "$";
+    private static final String REGEX_3 = "^" + COMPONENT_1 + COMPONENT_2 + COMPONENT_3 + "$";
+    private static final String[] MULTIPLE_REGEX = new String[] {REGEX_1, REGEX_2, REGEX_3};
+
+    /**
+     * Test instance methods with single regular expression.
+     */
+    @Test
+    public void testSingle() {
+        RegexValidator sensitive   = new RegexValidator(REGEX);
+        RegexValidator insensitive = new RegexValidator(REGEX, false);
+
+        // isValid()
+        assertEquals("Sensitive isValid() valid",     true,   sensitive.isValid("ac-DE-1"));
+        assertEquals("Sensitive isValid() invalid",   false,  sensitive.isValid("AB-de-1"));
+        assertEquals("Insensitive isValid() valid",   true,   insensitive.isValid("AB-de-1"));
+        assertEquals("Insensitive isValid() invalid", false,  insensitive.isValid("ABd-de-1"));
+
+        // validate()
+        assertEquals("Sensitive validate() valid",     "acDE1", sensitive.validate("ac-DE-1"));
+        assertEquals("Sensitive validate() invalid",   null,    sensitive.validate("AB-de-1"));
+        assertEquals("Insensitive validate() valid",   "ABde1", insensitive.validate("AB-de-1"));
+        assertEquals("Insensitive validate() invalid", null,    insensitive.validate("ABd-de-1"));
+
+        // match()
+        checkArray("Sensitive match() valid",     new String[] {"ac", "DE", "1"}, sensitive.match("ac-DE-1"));
+        checkArray("Sensitive match() invalid",   null,                           sensitive.match("AB-de-1"));
+        checkArray("Insensitive match() valid",   new String[] {"AB", "de", "1"}, insensitive.match("AB-de-1"));
+        checkArray("Insensitive match() invalid", null,                           insensitive.match("ABd-de-1"));
+        assertEquals("validate one", "ABC", (new RegexValidator("^([A-Z]*)$")).validate("ABC"));
+        checkArray("match one", new String[] {"ABC"}, (new RegexValidator("^([A-Z]*)$")).match("ABC"));
+    }
+
+    /**
+     * Test with multiple regular expressions (case sensitive).
+     */
+    @Test
+    public void testMultipleSensitive() {
+
+        // ------------ Set up Sensitive Validators
+        RegexValidator multiple   = new RegexValidator(MULTIPLE_REGEX);
+        RegexValidator single1   = new RegexValidator(REGEX_1);
+        RegexValidator single2   = new RegexValidator(REGEX_2);
+        RegexValidator single3   = new RegexValidator(REGEX_3);
+
+        // ------------ Set up test values
+        String value = "aac FDE 321";
+        String expect = "aacFDE321";
+        String[] array = new String[] {"aac", "FDE", "321"};
+
+        // isValid()
+        assertEquals("Sensitive isValid() Multiple", true,  multiple.isValid(value));
+        assertEquals("Sensitive isValid() 1st",      false, single1.isValid(value));
+        assertEquals("Sensitive isValid() 2nd",      true,  single2.isValid(value));
+        assertEquals("Sensitive isValid() 3rd",      false, single3.isValid(value));
+
+        // validate()
+        assertEquals("Sensitive validate() Multiple", expect, multiple.validate(value));
+        assertNull("Sensitive validate() 1st",      single1.validate(value));
+        assertEquals("Sensitive validate() 2nd",      expect, single2.validate(value));
+        assertNull("Sensitive validate() 3rd",      single3.validate(value));
+
+        // match()
+        checkArray("Sensitive match() Multiple", array, multiple.match(value));
+        checkArray("Sensitive match() 1st",      null,  single1.match(value));
+        checkArray("Sensitive match() 2nd",      array, single2.match(value));
+        checkArray("Sensitive match() 3rd",      null,  single3.match(value));
+
+        // All invalid
+        value = "AAC*FDE*321";
+        assertEquals("isValid() Invalid",  false, multiple.isValid(value));
+        assertNull("validate() Invalid", multiple.validate(value));
+        assertNull("match() Multiple",   multiple.match(value));
+    }
+
+    /**
+     * Test with multiple regular expressions (case in-sensitive).
+     */
+    @Test
+    public void testMultipleInsensitive() {
+
+        // ------------ Set up In-sensitive Validators
+        RegexValidator multiple = new RegexValidator(MULTIPLE_REGEX, false);
+        RegexValidator single1   = new RegexValidator(REGEX_1, false);
+        RegexValidator single2   = new RegexValidator(REGEX_2, false);
+        RegexValidator single3   = new RegexValidator(REGEX_3, false);
+
+        // ------------ Set up test values
+        String value = "AAC FDE 321";
+        String expect = "AACFDE321";
+        String[] array = new String[] {"AAC", "FDE", "321"};
+
+        // isValid()
+        assertEquals("isValid() Multiple", true,  multiple.isValid(value));
+        assertEquals("isValid() 1st",      false, single1.isValid(value));
+        assertEquals("isValid() 2nd",      true,  single2.isValid(value));
+        assertEquals("isValid() 3rd",      false, single3.isValid(value));
+
+        // validate()
+        assertEquals("validate() Multiple", expect, multiple.validate(value));
+        assertNull("validate() 1st",      single1.validate(value));
+        assertEquals("validate() 2nd",      expect, single2.validate(value));
+        assertNull("validate() 3rd",      single3.validate(value));
+
+        // match()
+        checkArray("match() Multiple", array, multiple.match(value));
+        checkArray("match() 1st",      null,  single1.match(value));
+        checkArray("match() 2nd",      array, single2.match(value));
+        checkArray("match() 3rd",      null,  single3.match(value));
+
+        // All invalid
+        value = "AAC*FDE*321";
+        assertEquals("isValid() Invalid",  false, multiple.isValid(value));
+        assertNull("validate() Invalid", multiple.validate(value));
+        assertNull("match() Multiple",   multiple.match(value));
+    }
+
+    /**
+     * Test Null value
+     */
+    @Test
+    public void testNullValue() {
+
+        RegexValidator validator = new RegexValidator(REGEX);
+        assertEquals("Instance isValid()",  false, validator.isValid(null));
+        assertNull("Instance validate()", validator.validate(null));
+        assertNull("Instance match()",    validator.match(null));
+    }
+
+    /**
+     * Test exceptions
+     */
+    @Test
+    public void testMissingRegex() {
+
+        // Single Regular Expression - null
+        try {
+            new RegexValidator((String) null);
+            fail("Single Null - expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Single Null", "Regular expression[0] is missing", e.getMessage());
+        }
+
+        // Single Regular Expression - Zero Length
+        try {
+            new RegexValidator("");
+            fail("Single Zero Length - expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Single Zero Length", "Regular expression[0] is missing", e.getMessage());
+        }
+
+        // Multiple Regular Expression - Null array
+        try {
+            new RegexValidator((String[]) null);
+            fail("Null Array - expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Null Array", "Regular expressions are missing", e.getMessage());
+        }
+
+        // Multiple Regular Expression - Zero Length array
+        try {
+            new RegexValidator(new String[0]);
+            fail("Zero Length Array - expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Zero Length Array", "Regular expressions are missing", e.getMessage());
+        }
+
+        // Multiple Regular Expression - Array has Null
+        String[] expressions = new String[] {"ABC", null};
+        try {
+            new RegexValidator(expressions);
+            fail("Array has Null - expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Array has Null", "Regular expression[1] is missing", e.getMessage());
+        }
+
+        // Multiple Regular Expression - Array has Zero Length
+        expressions = new String[] {"", "ABC"};
+        try {
+            new RegexValidator(expressions);
+            fail("Array has Zero Length - expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Array has Zero Length", "Regular expression[0] is missing", e.getMessage());
+        }
+    }
+
+    /**
+     * Test exceptions
+     */
+    @Test
+    public void testExceptions() {
+        String invalidRegex = "^([abCD12]*$";
+        try {
+            new RegexValidator(invalidRegex);
+        } catch (PatternSyntaxException e) {
+            // expected
+            Main.debug(e.getMessage());
+        }
+    }
+
+    /**
+     * Test toString() method
+     */
+    @Test
+    public void testToString() {
+        RegexValidator single = new RegexValidator(REGEX);
+        assertEquals("Single", "RegexValidator{" + REGEX + "}", single.toString());
+
+        RegexValidator multiple = new RegexValidator(new String[] {REGEX, REGEX});
+        assertEquals("Multiple", "RegexValidator{" + REGEX + "," + REGEX + "}", multiple.toString());
+    }
+
+    /**
+     * Compare two arrays
+     * @param label Label for the test
+     * @param expect Expected array
+     * @param result Actual array
+     */
+    private void checkArray(String label, String[] expect, String[] result) {
+
+        // Handle nulls
+        if (expect == null || result == null) {
+            if (expect == null && result == null) {
+                return; // valid, both null
+            } else {
+                fail(label + " Null expect=" + Arrays.toString(expect) + " result=" + Arrays.toString(result));
+            }
+            return; // not strictly necessary, but prevents possible NPE below
+        }
+
+        // Check Length
+        if (expect.length != result.length) {
+            fail(label + " Length expect=" + expect.length + " result=" + result.length);
+        }
+
+        // Check Values
+        for (int i = 0; i < expect.length; i++) {
+            assertEquals(label +" value[" + i + "]", expect[i], result[i]);
+        }
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/ResultPair.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/ResultPair.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/ResultPair.java	(revision 9853)
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Groups tests and expected results.
+ *
+ * @version $Revision: 1713567 $
+ */
+class ResultPair {
+    /** item */
+    public final String item;
+    /** valid */
+    public final boolean valid;
+
+    /**
+     * Constructs a new {@code ResultPair}.
+     * @param item item
+     * @param valid valid
+     */
+    ResultPair(String item, boolean valid) {
+        this.item = item;
+        this.valid = valid; //Whether the individual part of url is valid.
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/UrlValidatorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/UrlValidatorTest.java	(revision 9853)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/routines/UrlValidatorTest.java	(revision 9853)
@@ -0,0 +1,654 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Performs Validation Test for url validations.
+ *
+ * @version $Revision: 1715076 $
+ */
+public class UrlValidatorTest {
+
+   private final boolean printStatus = false;
+   private final boolean printIndex = false; //print index that indicates current scheme,host,port,path, query test were using.
+
+   /**
+    * Setup
+    */
+   @Before
+   public void setUp() {
+      for (int index = 0; index < testPartsIndex.length - 1; index++) {
+         testPartsIndex[index] = 0;
+      }
+   }
+
+   /**
+    * Test is valid
+    */
+   @Test
+   public void testIsValid() {
+        testIsValid(testUrlParts, UrlValidator.ALLOW_ALL_SCHEMES);
+        setUp();
+        long options =
+            UrlValidator.ALLOW_2_SLASHES
+                + UrlValidator.ALLOW_ALL_SCHEMES
+                + UrlValidator.NO_FRAGMENTS;
+
+        testIsValid(testUrlPartsOptions, options);
+   }
+
+   /**
+    * Test is valid scheme
+    */
+   @Test
+   public void testIsValidScheme() {
+      if (printStatus) {
+         System.out.print("\n testIsValidScheme() ");
+      }
+      //UrlValidator urlVal = new UrlValidator(schemes,false,false,false);
+      UrlValidator urlVal = new UrlValidator(schemes, 0);
+      for (int sIndex = 0; sIndex < testScheme.length; sIndex++) {
+         ResultPair testPair = testScheme[sIndex];
+         boolean result = urlVal.isValidScheme(testPair.item);
+         assertEquals(testPair.item, testPair.valid, result);
+         if (printStatus) {
+            if (result == testPair.valid) {
+               System.out.print('.');
+            } else {
+               System.out.print('X');
+            }
+         }
+      }
+      if (printStatus) {
+         System.out.println();
+      }
+   }
+
+   /**
+    * Create set of tests by taking the testUrlXXX arrays and
+    * running through all possible permutations of their combinations.
+    *
+    * @param testObjects Used to create a url.
+    * @param options options
+    */
+   private void testIsValid(Object[] testObjects, long options) {
+      UrlValidator urlVal = new UrlValidator(null, null, options);
+      assertTrue(urlVal.isValid("http://www.google.com"));
+      assertTrue(urlVal.isValid("http://www.google.com/"));
+      int statusPerLine = 60;
+      int printed = 0;
+      if (printIndex)  {
+         statusPerLine = 6;
+      }
+      do {
+          StringBuilder testBuffer = new StringBuilder();
+         boolean expected = true;
+         for (int testPartsIndexIndex = 0; testPartsIndexIndex < testPartsIndex.length; ++testPartsIndexIndex) {
+            int index = testPartsIndex[testPartsIndexIndex];
+            ResultPair[] part = (ResultPair[]) testObjects[testPartsIndexIndex];
+            testBuffer.append(part[index].item);
+            expected &= part[index].valid;
+         }
+         String url = testBuffer.toString();
+         boolean result = urlVal.isValid(url);
+         assertEquals(url, expected, result);
+         if (printStatus) {
+            if (printIndex) {
+               System.out.print(testPartsIndextoString());
+            } else {
+               if (result == expected) {
+                  System.out.print('.');
+               } else {
+                  System.out.print('X');
+               }
+            }
+            printed++;
+            if (printed == statusPerLine) {
+               System.out.println();
+               printed = 0;
+            }
+         }
+      } while (incrementTestPartsIndex(testPartsIndex, testObjects));
+      if (printStatus) {
+         System.out.println();
+      }
+   }
+
+    /**
+     * Non-regression test for VALIDATOR-202
+     */
+    @Test
+    public void testValidator202() {
+        String[] schemes = {"http", "https"};
+        UrlValidator urlValidator = new UrlValidator(schemes, UrlValidator.NO_FRAGMENTS);
+        assertTrue(urlValidator.isValid(
+          "http://l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.org"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-204
+     */
+    @Test
+    public void testValidator204() {
+        String[] schemes = {"http", "https"};
+        UrlValidator urlValidator = new UrlValidator(schemes);
+        assertTrue(urlValidator.isValid("http://tech.yahoo.com/rc/desktops/102;_ylt=Ao8yevQHlZ4On0O3ZJGXLEQFLZA5"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-218
+     */
+    @Test
+    public void testValidator218() {
+        UrlValidator validator = new UrlValidator(UrlValidator.ALLOW_2_SLASHES);
+        assertTrue("parentheses should be valid in URLs",
+               validator.isValid("http://somewhere.com/pathxyz/file(1).html"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-235
+     */
+    @Test
+    public void testValidator235() {
+        String version = System.getProperty("java.version");
+        if (version.compareTo("1.6") < 0) {
+            System.out.println("Cannot run Unicode IDN tests");
+            return; // Cannot run the test
+        }
+        UrlValidator validator = new UrlValidator();
+        assertTrue("xn--d1abbgf6aiiy.xn--p1ai should validate", validator.isValid("http://xn--d1abbgf6aiiy.xn--p1ai"));
+        assertTrue("президент.рф should validate", validator.isValid("http://президент.рф"));
+        assertTrue("www.b\u00fccher.ch should validate", validator.isValid("http://www.b\u00fccher.ch"));
+        assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("http://www.\uFFFD.ch"));
+        assertTrue("www.b\u00fccher.ch should validate", validator.isValid("ftp://www.b\u00fccher.ch"));
+        assertFalse("www.\uFFFD.ch FFFD should fail", validator.isValid("ftp://www.\uFFFD.ch"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-248
+     */
+    @Test
+    public void testValidator248() {
+        RegexValidator regex = new RegexValidator(new String[] {"localhost", ".*\\.my-testing"});
+        UrlValidator validator = new UrlValidator(regex, 0);
+
+        assertTrue("localhost URL should validate",
+                validator.isValid("http://localhost/test/index.html"));
+        assertTrue("first.my-testing should validate",
+                validator.isValid("http://first.my-testing/test/index.html"));
+        assertTrue("sup3r.my-testing should validate",
+                validator.isValid("http://sup3r.my-testing/test/index.html"));
+
+        assertFalse("broke.my-test should not validate",
+                validator.isValid("http://broke.my-test/test/index.html"));
+
+        assertTrue("www.apache.org should still validate",
+                validator.isValid("http://www.apache.org/test/index.html"));
+
+        // Now check using options
+        validator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS);
+
+        assertTrue("localhost URL should validate",
+              validator.isValid("http://localhost/test/index.html"));
+
+        assertTrue("machinename URL should validate",
+              validator.isValid("http://machinename/test/index.html"));
+
+        assertTrue("www.apache.org should still validate",
+              validator.isValid("http://www.apache.org/test/index.html"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-288
+     */
+    @Test
+    public void testValidator288() {
+        UrlValidator validator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS);
+
+        assertTrue("hostname should validate",
+                validator.isValid("http://hostname"));
+
+        assertTrue("hostname with path should validate",
+                validator.isValid("http://hostname/test/index.html"));
+
+        assertTrue("localhost URL should validate",
+                validator.isValid("http://localhost/test/index.html"));
+
+        assertFalse("first.my-testing should not validate",
+                validator.isValid("http://first.my-testing/test/index.html"));
+
+        assertFalse("broke.hostname should not validate",
+                validator.isValid("http://broke.hostname/test/index.html"));
+
+        assertTrue("www.apache.org should still validate",
+                validator.isValid("http://www.apache.org/test/index.html"));
+
+        // Turn it off, and check
+        validator = new UrlValidator(0);
+
+        assertFalse("hostname should no longer validate",
+                validator.isValid("http://hostname"));
+
+        assertFalse("localhost URL should no longer validate",
+                validator.isValid("http://localhost/test/index.html"));
+
+        assertTrue("www.apache.org should still validate",
+                validator.isValid("http://www.apache.org/test/index.html"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-276
+     */
+    @Test
+    public void testValidator276() {
+        // file:// isn't allowed by default
+        UrlValidator validator = new UrlValidator();
+
+        assertTrue("http://apache.org/ should be allowed by default",
+                 validator.isValid("http://www.apache.org/test/index.html"));
+
+        assertFalse("file:///c:/ shouldn't be allowed by default",
+                 validator.isValid("file:///C:/some.file"));
+
+        assertFalse("file:///c:\\ shouldn't be allowed by default",
+              validator.isValid("file:///C:\\some.file"));
+
+        assertFalse("file:///etc/ shouldn't be allowed by default",
+              validator.isValid("file:///etc/hosts"));
+
+        assertFalse("file://localhost/etc/ shouldn't be allowed by default",
+              validator.isValid("file://localhost/etc/hosts"));
+
+        assertFalse("file://localhost/c:/ shouldn't be allowed by default",
+              validator.isValid("file://localhost/c:/some.file"));
+
+        // Turn it on, and check
+        // Note - we need to enable local urls when working with file:
+        validator = new UrlValidator(new String[] {"http", "file"}, UrlValidator.ALLOW_LOCAL_URLS);
+
+        assertTrue("http://apache.org/ should be allowed by default",
+                 validator.isValid("http://www.apache.org/test/index.html"));
+
+        assertTrue("file:///c:/ should now be allowed",
+                 validator.isValid("file:///C:/some.file"));
+
+        // Currently, we don't support the c:\ form
+        assertFalse("file:///c:\\ shouldn't be allowed",
+              validator.isValid("file:///C:\\some.file"));
+
+        assertTrue("file:///etc/ should now be allowed",
+              validator.isValid("file:///etc/hosts"));
+
+        assertTrue("file://localhost/etc/ should now be allowed",
+              validator.isValid("file://localhost/etc/hosts"));
+
+        assertTrue("file://localhost/c:/ should now be allowed",
+              validator.isValid("file://localhost/c:/some.file"));
+
+        // These are never valid
+        assertFalse("file://c:/ shouldn't ever be allowed, needs file:///c:/",
+              validator.isValid("file://C:/some.file"));
+
+        assertFalse("file://c:\\ shouldn't ever be allowed, needs file:///c:/",
+              validator.isValid("file://C:\\some.file"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-309
+     */
+    @Test
+    public void testValidator309() {
+        UrlValidator urlValidator = new UrlValidator();
+        assertTrue(urlValidator.isValid("http://sample.ondemand.com/"));
+        assertTrue(urlValidator.isValid("hTtP://sample.ondemand.CoM/"));
+        assertTrue(urlValidator.isValid("httpS://SAMPLE.ONEMAND.COM/"));
+        urlValidator = new UrlValidator(new String[] {"HTTP", "HTTPS"});
+        assertTrue(urlValidator.isValid("http://sample.ondemand.com/"));
+        assertTrue(urlValidator.isValid("hTtP://sample.ondemand.CoM/"));
+        assertTrue(urlValidator.isValid("httpS://SAMPLE.ONEMAND.COM/"));
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-339
+     */
+    @Test
+    public void testValidator339() {
+        UrlValidator urlValidator = new UrlValidator();
+        assertTrue(urlValidator.isValid("http://www.cnn.com/WORLD/?hpt=sitenav")); // without
+        assertTrue(urlValidator.isValid("http://www.cnn.com./WORLD/?hpt=sitenav")); // with
+        assertFalse(urlValidator.isValid("http://www.cnn.com../")); // doubly dotty
+        assertFalse(urlValidator.isValid("http://www.cnn.invalid/"));
+        assertFalse(urlValidator.isValid("http://www.cnn.invalid./")); // check . does not affect invalid domains
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-339 - IDN
+     */
+    @Test
+    public void testValidator339IDN() {
+        UrlValidator urlValidator = new UrlValidator();
+        assertTrue(urlValidator.isValid("http://президент.рф/WORLD/?hpt=sitenav")); // without
+        assertTrue(urlValidator.isValid("http://президент.рф./WORLD/?hpt=sitenav")); // with
+        assertFalse(urlValidator.isValid("http://президент.рф..../")); // very dotty
+        assertFalse(urlValidator.isValid("http://президент.рф.../")); // triply dotty
+        assertFalse(urlValidator.isValid("http://президент.рф../")); // doubly dotty
+    }
+
+    /**
+     * Non-regression test for VALIDATOR-342
+     */
+    @Test
+    public void testValidator342() {
+        UrlValidator urlValidator = new UrlValidator();
+        assertTrue(urlValidator.isValid("http://example.rocks/"));
+        assertTrue(urlValidator.isValid("http://example.rocks"));
+    }
+
+    static boolean incrementTestPartsIndex(int[] testPartsIndex, Object[] testParts) {
+      boolean carry = true;  //add 1 to lowest order part.
+      boolean maxIndex = true;
+      for (int testPartsIndexIndex = testPartsIndex.length - 1; testPartsIndexIndex >= 0; --testPartsIndexIndex) {
+         int index = testPartsIndex[testPartsIndexIndex];
+         ResultPair[] part = (ResultPair[]) testParts[testPartsIndexIndex];
+         if (carry) {
+            if (index < part.length - 1) {
+               index++;
+               testPartsIndex[testPartsIndexIndex] = index;
+               carry = false;
+            } else {
+               testPartsIndex[testPartsIndexIndex] = 0;
+               carry = true;
+            }
+         }
+         maxIndex &= (index == (part.length - 1));
+      }
+
+      return (!maxIndex);
+   }
+
+   private String testPartsIndextoString() {
+       StringBuilder carryMsg = new StringBuilder("{");
+       for (int testPartsIndexIndex = 0; testPartsIndexIndex < testPartsIndex.length; ++testPartsIndexIndex) {
+         carryMsg.append(testPartsIndex[testPartsIndexIndex]);
+         if (testPartsIndexIndex < testPartsIndex.length - 1) {
+            carryMsg.append(',');
+         } else {
+            carryMsg.append('}');
+         }
+       }
+       return carryMsg.toString();
+   }
+
+   /**
+    * Test validate URL
+    */
+   @Test
+   public void testValidateUrl() {
+      assertTrue(true);
+   }
+
+   /**
+    * Non-regression test for VALIDATOR-290
+    */
+   @Test
+   public void testValidator290() {
+        UrlValidator validator = new UrlValidator();
+        assertTrue(validator.isValid("http://xn--h1acbxfam.idn.icann.org/"));
+//        assertTrue(validator.isValid("http://xn--e1afmkfd.xn--80akhbyknj4f"));
+        // Internationalized country code top-level domains
+        assertTrue(validator.isValid("http://test.xn--lgbbat1ad8j")); //Algeria
+        assertTrue(validator.isValid("http://test.xn--fiqs8s")); // China
+        assertTrue(validator.isValid("http://test.xn--fiqz9s")); // China
+        assertTrue(validator.isValid("http://test.xn--wgbh1c")); // Egypt
+        assertTrue(validator.isValid("http://test.xn--j6w193g")); // Hong Kong
+        assertTrue(validator.isValid("http://test.xn--h2brj9c")); // India
+        assertTrue(validator.isValid("http://test.xn--mgbbh1a71e")); // India
+        assertTrue(validator.isValid("http://test.xn--fpcrj9c3d")); // India
+        assertTrue(validator.isValid("http://test.xn--gecrj9c")); // India
+        assertTrue(validator.isValid("http://test.xn--s9brj9c")); // India
+        assertTrue(validator.isValid("http://test.xn--xkc2dl3a5ee0h")); // India
+        assertTrue(validator.isValid("http://test.xn--45brj9c")); // India
+        assertTrue(validator.isValid("http://test.xn--mgba3a4f16a")); // Iran
+        assertTrue(validator.isValid("http://test.xn--mgbayh7gpa")); // Jordan
+        assertTrue(validator.isValid("http://test.xn--mgbc0a9azcg")); // Morocco
+        assertTrue(validator.isValid("http://test.xn--ygbi2ammx")); // Palestinian Territory
+        assertTrue(validator.isValid("http://test.xn--wgbl6a")); // Qatar
+        assertTrue(validator.isValid("http://test.xn--p1ai")); // Russia
+        assertTrue(validator.isValid("http://test.xn--mgberp4a5d4ar")); //  Saudi Arabia
+        assertTrue(validator.isValid("http://test.xn--90a3ac")); // Serbia
+        assertTrue(validator.isValid("http://test.xn--yfro4i67o")); // Singapore
+        assertTrue(validator.isValid("http://test.xn--clchc0ea0b2g2a9gcd")); // Singapore
+        assertTrue(validator.isValid("http://test.xn--3e0b707e")); // South Korea
+        assertTrue(validator.isValid("http://test.xn--fzc2c9e2c")); // Sri Lanka
+        assertTrue(validator.isValid("http://test.xn--xkc2al3hye2a")); // Sri Lanka
+        assertTrue(validator.isValid("http://test.xn--ogbpf8fl")); // Syria
+        assertTrue(validator.isValid("http://test.xn--kprw13d")); // Taiwan
+        assertTrue(validator.isValid("http://test.xn--kpry57d")); // Taiwan
+        assertTrue(validator.isValid("http://test.xn--o3cw4h")); // Thailand
+        assertTrue(validator.isValid("http://test.xn--pgbs0dh")); // Tunisia
+        assertTrue(validator.isValid("http://test.xn--mgbaam7a8h")); // United Arab Emirates
+        // Proposed internationalized ccTLDs
+//        assertTrue(validator.isValid("http://test.xn--54b7fta0cc")); // Bangladesh
+//        assertTrue(validator.isValid("http://test.xn--90ae")); // Bulgaria
+//        assertTrue(validator.isValid("http://test.xn--node")); // Georgia
+//        assertTrue(validator.isValid("http://test.xn--4dbrk0ce")); // Israel
+//        assertTrue(validator.isValid("http://test.xn--mgb9awbf")); // Oman
+//        assertTrue(validator.isValid("http://test.xn--j1amh")); // Ukraine
+//        assertTrue(validator.isValid("http://test.xn--mgb2ddes")); // Yemen
+        // Test TLDs
+//        assertTrue(validator.isValid("http://test.xn--kgbechtv")); // Arabic
+//        assertTrue(validator.isValid("http://test.xn--hgbk6aj7f53bba")); // Persian
+//        assertTrue(validator.isValid("http://test.xn--0zwm56d")); // Chinese
+//        assertTrue(validator.isValid("http://test.xn--g6w251d")); // Chinese
+//        assertTrue(validator.isValid("http://test.xn--80akhbyknj4f")); // Russian
+//        assertTrue(validator.isValid("http://test.xn--11b5bs3a9aj6g")); // Hindi
+//        assertTrue(validator.isValid("http://test.xn--jxalpdlp")); // Greek
+//        assertTrue(validator.isValid("http://test.xn--9t4b11yi5a")); // Korean
+//        assertTrue(validator.isValid("http://test.xn--deba0ad")); // Yiddish
+//        assertTrue(validator.isValid("http://test.xn--zckzah")); // Japanese
+//        assertTrue(validator.isValid("http://test.xn--hlcj6aya9esc7a")); // Tamil
+    }
+
+   /**
+    * Non-regression test for VALIDATOR-361
+    */
+   @Test
+   public void testValidator361() {
+       UrlValidator validator = new UrlValidator();
+       assertTrue(validator.isValid("http://hello.tokyo/"));
+    }
+
+   /**
+    * Non-regression test for VALIDATOR-363
+    */
+   @Test
+   public void testValidator363() {
+        UrlValidator urlValidator = new UrlValidator();
+        assertTrue(urlValidator.isValid("http://www.example.org/a/b/hello..world"));
+        assertTrue(urlValidator.isValid("http://www.example.org/a/hello..world"));
+        assertTrue(urlValidator.isValid("http://www.example.org/hello.world/"));
+        assertTrue(urlValidator.isValid("http://www.example.org/hello..world/"));
+        assertTrue(urlValidator.isValid("http://www.example.org/hello.world"));
+        assertTrue(urlValidator.isValid("http://www.example.org/hello..world"));
+        assertTrue(urlValidator.isValid("http://www.example.org/..world"));
+        assertTrue(urlValidator.isValid("http://www.example.org/.../world"));
+        assertFalse(urlValidator.isValid("http://www.example.org/../world"));
+        assertFalse(urlValidator.isValid("http://www.example.org/.."));
+        assertFalse(urlValidator.isValid("http://www.example.org/../"));
+        assertFalse(urlValidator.isValid("http://www.example.org/./.."));
+        assertFalse(urlValidator.isValid("http://www.example.org/././.."));
+        assertTrue(urlValidator.isValid("http://www.example.org/..."));
+        assertTrue(urlValidator.isValid("http://www.example.org/.../"));
+        assertTrue(urlValidator.isValid("http://www.example.org/.../.."));
+    }
+
+   /**
+    * Non-regression test for VALIDATOR-375
+    */
+   @Test
+   public void testValidator375() {
+       UrlValidator validator = new UrlValidator();
+       String url = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html";
+       assertTrue("IPv6 address URL should validate: " + url, validator.isValid(url));
+       url = "http://[::1]:80/index.html";
+       assertTrue("IPv6 address URL should validate: " + url, validator.isValid(url));
+       url = "http://FEDC:BA98:7654:3210:FEDC:BA98:7654:3210:80/index.html";
+       assertFalse("IPv6 address without [] should not validate: " + url, validator.isValid(url));
+    }
+
+   /**
+    * Non-regression test for VALIDATOR-353
+    */
+   @Test
+   public void testValidator353() { // userinfo
+       UrlValidator validator = new UrlValidator();
+       assertTrue(validator.isValid("http://www.apache.org:80/path"));
+       assertTrue(validator.isValid("http://user:pass@www.apache.org:80/path"));
+       assertTrue(validator.isValid("http://user:@www.apache.org:80/path"));
+       assertTrue(validator.isValid("http://us%00er:-._~!$&'()*+,;=@www.apache.org:80/path"));
+       assertFalse(validator.isValid("http://:pass@www.apache.org:80/path"));
+       assertFalse(validator.isValid("http://:@www.apache.org:80/path"));
+       assertFalse(validator.isValid("http://user:pa:ss@www.apache.org/path"));
+       assertFalse(validator.isValid("http://user:pa@ss@www.apache.org/path"));
+   }
+
+   /**
+    * Non-regression test for VALIDATOR-382
+    */
+   @Test
+   public void testValidator382() {
+       UrlValidator validator = new UrlValidator();
+       assertTrue(validator.isValid("ftp://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose"));
+   }
+
+   /**
+    * Non-regression test for VALIDATOR-380
+    */
+   @Test
+   public void testValidator380() {
+       UrlValidator validator = new UrlValidator();
+       assertTrue(validator.isValid("http://www.apache.org:80/path"));
+       assertTrue(validator.isValid("http://www.apache.org:8/path"));
+       assertTrue(validator.isValid("http://www.apache.org:/path"));
+   }
+
+   //-------------------- Test data for creating a composite URL
+   /**
+    * The data given below approximates the 4 parts of a URL
+    * {@code <scheme>://<authority><path>?<query>} except that the port number
+    * is broken out of authority to increase the number of permutations.
+    * A complete URL is composed of a scheme+authority+port+path+query,
+    * all of which must be individually valid for the entire URL to be considered
+    * valid.
+    */
+   ResultPair[] testUrlScheme = {new ResultPair("http://", true),
+                               new ResultPair("ftp://", true),
+                               new ResultPair("h3t://", true),
+                               new ResultPair("3ht://", false),
+                               new ResultPair("http:/", false),
+                               new ResultPair("http:", false),
+                               new ResultPair("http/", false),
+                               new ResultPair("://", false),
+                               new ResultPair("", true)};
+
+   ResultPair[] testUrlAuthority = {new ResultPair("www.google.com", true),
+                                  new ResultPair("go.com", true),
+                                  new ResultPair("go.au", true),
+                                  new ResultPair("0.0.0.0", true),
+                                  new ResultPair("255.255.255.255", true),
+                                  new ResultPair("256.256.256.256", false),
+                                  new ResultPair("255.com", true),
+                                  new ResultPair("1.2.3.4.5", false),
+                                  new ResultPair("1.2.3.4.", false),
+                                  new ResultPair("1.2.3", false),
+                                  new ResultPair(".1.2.3.4", false),
+                                  new ResultPair("go.a", false),
+                                  new ResultPair("go.a1a", false),
+                                  new ResultPair("go.cc", true),
+                                  new ResultPair("go.1aa", false),
+                                  new ResultPair("aaa.", false),
+                                  new ResultPair(".aaa", false),
+                                  new ResultPair("aaa", false),
+                                  new ResultPair("", false)
+   };
+   ResultPair[] testUrlPort = {new ResultPair(":80", true),
+                             new ResultPair(":65535", true),
+                             new ResultPair(":0", true),
+                             new ResultPair("", true),
+                             new ResultPair(":-1", false),
+                             new ResultPair(":65636", true),
+                             new ResultPair(":65a", false)
+   };
+   ResultPair[] testPath = {new ResultPair("/test1", true),
+                          new ResultPair("/t123", true),
+                          new ResultPair("/$23", true),
+                          new ResultPair("/..", false),
+                          new ResultPair("/../", false),
+                          new ResultPair("/test1/", true),
+                          new ResultPair("", true),
+                          new ResultPair("/test1/file", true),
+                          new ResultPair("/..//file", false),
+                          new ResultPair("/test1//file", false)
+   };
+   //Test allow2slash, noFragment
+   ResultPair[] testUrlPathOptions = {new ResultPair("/test1", true),
+                                    new ResultPair("/t123", true),
+                                    new ResultPair("/$23", true),
+                                    new ResultPair("/..", false),
+                                    new ResultPair("/../", false),
+                                    new ResultPair("/test1/", true),
+                                    new ResultPair("/#", false),
+                                    new ResultPair("", true),
+                                    new ResultPair("/test1/file", true),
+                                    new ResultPair("/t123/file", true),
+                                    new ResultPair("/$23/file", true),
+                                    new ResultPair("/../file", false),
+                                    new ResultPair("/..//file", false),
+                                    new ResultPair("/test1//file", true),
+                                    new ResultPair("/#/file", false)
+   };
+
+   ResultPair[] testUrlQuery = {new ResultPair("?action=view", true),
+                              new ResultPair("?action=edit&mode=up", true),
+                              new ResultPair("", true)
+   };
+
+   Object[] testUrlParts = {testUrlScheme, testUrlAuthority, testUrlPort, testPath, testUrlQuery};
+   Object[] testUrlPartsOptions = {testUrlScheme, testUrlAuthority, testUrlPort, testUrlPathOptions, testUrlQuery};
+   int[] testPartsIndex = {0, 0, 0, 0, 0};
+
+   //---------------- Test data for individual url parts ----------------
+   private final String[] schemes = {"http", "gopher", "g0-To+.",
+                                      "not_valid" // TODO this will need to be dropped if the ctor validates schemes
+                                    };
+
+   ResultPair[] testScheme = {new ResultPair("http", true),
+                            new ResultPair("ftp", false),
+                            new ResultPair("httpd", false),
+                            new ResultPair("gopher", true),
+                            new ResultPair("g0-to+.", true),
+                            new ResultPair("not_valid", false), // underscore not allowed
+                            new ResultPair("HtTp", true),
+                            new ResultPair("telnet", false)};
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/InternetTagsTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/InternetTagsTest.java	(revision 9852)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/InternetTagsTest.java	(revision 9853)
@@ -31,10 +31,8 @@
 
     /**
-     * Test of "Internet Tags" validation test.
+     * Test of valid URLs.
      */
     @Test
-    public void test() {
-
-        // Valid URLs
+    public void testValidUrls() {
         testUrl("url", "www.domain.com", true);                                // No protocol
         testUrl("url", "http://josm.openstreetmap.de", true);                  // Simple HTTP
@@ -47,14 +45,30 @@
         testUrl("website", "http://золотаяцепь.рф", true);                     // see #10862: IDN URL in Unicode form
         testUrl("website", "http://золотаяцепь.рф/", true);                    // see #10862: IDN URL in Unicode form + slash
+        testUrl("website", "http://www.dasideenreich.online", true);           // see #12257: new TLD added August 19, 2015
+    }
 
-        // Invalid URLs
+    /**
+     * Test of invalid URLs.
+     */
+    @Test
+    public void testInvalidUrls() {
         testUrl("url", "something://www.domain.com", false);                   // invalid protocol
         testUrl("url", "http://www.domain.invalidtld", false);                 // invalid TLD
+    }
 
-        // Valid E-mails
+    /**
+     * Test of valid e-mails.
+     */
+    @Test
+    public void testValidEmails() {
         testEmail("email", "contact@www.domain.com", true);                    // Simple email
         testEmail("contact:email", "john.doe@other-domain.org", true);         // Key with : + dash in domain
+    }
 
-        // Invalid E-mails
+    /**
+     * Test of invalid e-mails.
+     */
+    @Test
+    public void testInvalidEmails() {
         testEmail("email", "contact at www.domain.com", false);                // No @
         testEmail("contact:email", "john.doe@other-domain.invalidtld", false); // invalid TLD
Index: /trunk/tools/checkstyle/josm_filters.xml
===================================================================
--- /trunk/tools/checkstyle/josm_filters.xml	(revision 9852)
+++ /trunk/tools/checkstyle/josm_filters.xml	(revision 9853)
@@ -9,9 +9,10 @@
   <suppress checks="HeaderCheck" files="NTV2SubGrid\.java" />
   <suppress checks="HeaderCheck" files="NTV2Util\.java" />
-  <suppress checks="HeaderCheck" files="DomainValidator\.java" />
-  <suppress checks="HeaderCheck" files="EmailValidator\.java" />
-  <suppress checks="HeaderCheck" files="InetAddressValidator\.java" />
-  <suppress checks="HeaderCheck" files="RegexValidator\.java" />
-  <suppress checks="HeaderCheck" files="UrlValidator\.java" />
+  <suppress checks="HeaderCheck" files="DomainValidator(Test|IT)?\.java" />
+  <suppress checks="HeaderCheck" files="EmailValidator(Test)?\.java" />
+  <suppress checks="HeaderCheck" files="InetAddressValidator(Test)?\.java" />
+  <suppress checks="HeaderCheck" files="RegexValidator(Test)?\.java" />
+  <suppress checks="HeaderCheck" files="UrlValidator(Test)?\.java" />
+  <suppress checks="HeaderCheck" files="ResultPair\.java" />
   <suppress checks="HeaderCheck" files="Entities\.java" />
   <suppress checks="HeaderCheck" files="FileDrop\.java" />
