/*
 *  Copyright (C) 2011 The Libphonenumber Authors
 *
 *  Licensed 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 com.google.i18n.phonenumbers;

import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.regex.PatternSyntaxException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import junit.framework.TestCase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Unit tests for BuildMetadataFromXml.java
 *
 * @author Philippe Liard
 */
public class BuildMetadataFromXmlTest extends TestCase {

  // Helper method that outputs a DOM element from a XML string.
  private static Element parseXmlString(String xmlString)
      throws ParserConfigurationException, SAXException, IOException {
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
    InputSource inputSource = new InputSource();
    inputSource.setCharacterStream(new StringReader(xmlString));
    return documentBuilder.parse(inputSource).getDocumentElement();
  }

  // Tests validateRE().
  public void testValidateRERemovesWhiteSpaces() {
    String input = " hello world ";
    // Should remove all the white spaces contained in the provided string.
    assertEquals("helloworld", BuildMetadataFromXml.validateRE(input, true));
    // Make sure it only happens when the last parameter is set to true.
    assertEquals(" hello world ", BuildMetadataFromXml.validateRE(input, false));
  }

  public void testValidateREThrowsException() {
    String invalidPattern = "[";
    // Should throw an exception when an invalid pattern is provided independently of the last
    // parameter (remove white spaces).
    try {
      BuildMetadataFromXml.validateRE(invalidPattern, false);
      fail();
    } catch (PatternSyntaxException e) {
      // Test passed.
    }
    try {
      BuildMetadataFromXml.validateRE(invalidPattern, true);
      fail();
    } catch (PatternSyntaxException e) {
      // Test passed.
    }
    // We don't allow | to be followed by ) because it introduces bugs, since we typically use it at
    // the end of each line and when a line is deleted, if the pipe from the previous line is not
    // removed, we end up erroneously accepting an empty group as well.
    String patternWithPipeFollowedByClosingParentheses = "|)";
    try {
      BuildMetadataFromXml.validateRE(patternWithPipeFollowedByClosingParentheses, true);
      fail();
    } catch (PatternSyntaxException e) {
      // Test passed.
    }
    String patternWithPipeFollowedByNewLineAndClosingParentheses = "|\n)";
    try {
      BuildMetadataFromXml.validateRE(patternWithPipeFollowedByNewLineAndClosingParentheses, true);
      fail();
    } catch (PatternSyntaxException e) {
      // Test passed.
    }
  }

  public void testValidateRE() {
    String validPattern = "[a-zA-Z]d{1,9}";
    // The provided pattern should be left unchanged.
    assertEquals(validPattern, BuildMetadataFromXml.validateRE(validPattern, false));
  }

  // Tests getNationalPrefix().
  public void testGetNationalPrefix()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory nationalPrefix='00'/>";
    Element territoryElement = parseXmlString(xmlInput);
    assertEquals("00", BuildMetadataFromXml.getNationalPrefix(territoryElement));
  }

  // Tests loadTerritoryTagMetadata().
  public void testLoadTerritoryTagMetadata()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory"
        + "  countryCode='33' leadingDigits='2' internationalPrefix='00'"
        + "  preferredInternationalPrefix='0011' nationalPrefixForParsing='0'"
        + "  nationalPrefixTransformRule='9$1'"  // nationalPrefix manually injected.
        + "  preferredExtnPrefix=' x' mainCountryForCode='true'"
        + "  leadingZeroPossible='true' mobileNumberPortableRegion='true'>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder phoneMetadata =
        BuildMetadataFromXml.loadTerritoryTagMetadata("33", territoryElement, "0");
    assertEquals(33, phoneMetadata.getCountryCode());
    assertEquals("2", phoneMetadata.getLeadingDigits());
    assertEquals("00", phoneMetadata.getInternationalPrefix());
    assertEquals("0011", phoneMetadata.getPreferredInternationalPrefix());
    assertEquals("0", phoneMetadata.getNationalPrefixForParsing());
    assertEquals("9$1", phoneMetadata.getNationalPrefixTransformRule());
    assertEquals("0", phoneMetadata.getNationalPrefix());
    assertEquals(" x", phoneMetadata.getPreferredExtnPrefix());
    assertTrue(phoneMetadata.getMainCountryForCode());
    assertTrue(phoneMetadata.isMobileNumberPortableRegion());
  }

  public void testLoadTerritoryTagMetadataSetsBooleanFieldsToFalseByDefault()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode='33'/>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder phoneMetadata =
        BuildMetadataFromXml.loadTerritoryTagMetadata("33", territoryElement, "");
    assertFalse(phoneMetadata.getMainCountryForCode());
    assertFalse(phoneMetadata.isMobileNumberPortableRegion());
  }

  public void testLoadTerritoryTagMetadataSetsNationalPrefixForParsingByDefault()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode='33'/>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder phoneMetadata =
        BuildMetadataFromXml.loadTerritoryTagMetadata("33", territoryElement, "00");
    // When unspecified, nationalPrefixForParsing defaults to nationalPrefix.
    assertEquals("00", phoneMetadata.getNationalPrefix());
    assertEquals(phoneMetadata.getNationalPrefix(), phoneMetadata.getNationalPrefixForParsing());
  }

  public void testLoadTerritoryTagMetadataWithRequiredAttributesOnly()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode='33' internationalPrefix='00'/>";
    Element territoryElement = parseXmlString(xmlInput);
    // Should not throw any exception.
    BuildMetadataFromXml.loadTerritoryTagMetadata("33", territoryElement, "");
  }

  // Tests loadInternationalFormat().
  public void testLoadInternationalFormat()
      throws ParserConfigurationException, SAXException, IOException {
    String intlFormat = "$1 $2";
    String xmlInput = "<numberFormat><intlFormat>" + intlFormat + "</intlFormat></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat nationalFormat = NumberFormat.newBuilder().build();

    assertTrue(BuildMetadataFromXml.loadInternationalFormat(metadata, numberFormatElement,
                                                            nationalFormat));
    assertEquals(intlFormat, metadata.getIntlNumberFormat(0).getFormat());
  }

  public void testLoadInternationalFormatWithBothNationalAndIntlFormatsDefined()
      throws ParserConfigurationException, SAXException, IOException {
    String intlFormat = "$1 $2";
    String xmlInput = "<numberFormat><intlFormat>" + intlFormat + "</intlFormat></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder nationalFormat = NumberFormat.newBuilder();
    nationalFormat.setFormat("$1");

    assertTrue(BuildMetadataFromXml.loadInternationalFormat(metadata, numberFormatElement,
                                                            nationalFormat.build()));
    assertEquals(intlFormat, metadata.getIntlNumberFormat(0).getFormat());
  }

  public void testLoadInternationalFormatExpectsOnlyOnePattern()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat><intlFormat/><intlFormat/></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();

    // Should throw an exception as multiple intlFormats are provided.
    try {
      BuildMetadataFromXml.loadInternationalFormat(metadata, numberFormatElement,
                                                   NumberFormat.newBuilder().build());
      fail();
    } catch (RuntimeException e) {
      // Test passed.
    }
  }

  public void testLoadInternationalFormatUsesNationalFormatByDefault()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder nationalFormat = NumberFormat.newBuilder();
    String nationalPattern = "$1 $2 $3";
    nationalFormat.setFormat(nationalPattern);

    assertFalse(BuildMetadataFromXml.loadInternationalFormat(metadata, numberFormatElement,
                                                             nationalFormat.build()));
    assertEquals(nationalPattern, metadata.getIntlNumberFormat(0).getFormat());
  }

  public void testLoadInternationalFormatCopiesNationalFormatData()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder nationalFormat = NumberFormat.newBuilder();
    nationalFormat.setFormat("$1-$2");
    nationalFormat.setNationalPrefixOptionalWhenFormatting(true);

    assertFalse(BuildMetadataFromXml.loadInternationalFormat(metadata, numberFormatElement,
                                                             nationalFormat.build()));
    assertTrue(metadata.getIntlNumberFormat(0).getNationalPrefixOptionalWhenFormatting());
  }

  public void testLoadNationalFormat()
      throws ParserConfigurationException, SAXException, IOException {
    String nationalFormat = "$1 $2";
    String xmlInput = String.format("<numberFormat><format>%s</format></numberFormat>",
                                    nationalFormat);
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder numberFormat = NumberFormat.newBuilder();
    BuildMetadataFromXml.loadNationalFormat(metadata, numberFormatElement, numberFormat);
    assertEquals(nationalFormat, numberFormat.getFormat());
  }

  public void testLoadNationalFormatRequiresFormat()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder numberFormat = NumberFormat.newBuilder();

    try {
      BuildMetadataFromXml.loadNationalFormat(metadata, numberFormatElement, numberFormat);
      fail();
    } catch (RuntimeException e) {
      // Test passed.
    }
  }

  public void testLoadNationalFormatExpectsExactlyOneFormat()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat><format/><format/></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder numberFormat = NumberFormat.newBuilder();

    try {
      BuildMetadataFromXml.loadNationalFormat(metadata, numberFormatElement, numberFormat);
      fail();
    } catch (RuntimeException e) {
      // Test passed.
    }
  }

  // Tests loadAvailableFormats().
  public void testLoadAvailableFormats()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory>"
        + "  <availableFormats>"
        + "    <numberFormat nationalPrefixFormattingRule='($FG)'"
        + "                  carrierCodeFormattingRule='$NP $CC ($FG)'>"
        + "      <format>$1 $2 $3</format>"
        + "    </numberFormat>"
        + "  </availableFormats>"
        + "</territory>";
    Element element = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.loadAvailableFormats(
        metadata, element, "0", "", false /* NP not optional */);
    assertEquals("($1)", metadata.getNumberFormat(0).getNationalPrefixFormattingRule());
    assertEquals("0 $CC ($1)", metadata.getNumberFormat(0).getDomesticCarrierCodeFormattingRule());
    assertEquals("$1 $2 $3", metadata.getNumberFormat(0).getFormat());
  }

  public void testLoadAvailableFormatsPropagatesCarrierCodeFormattingRule()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput =
        "<territory carrierCodeFormattingRule='$NP $CC ($FG)'>"
        + "  <availableFormats>"
        + "    <numberFormat nationalPrefixFormattingRule='($FG)'>"
        + "      <format>$1 $2 $3</format>"
        + "    </numberFormat>"
        + "  </availableFormats>"
        + "</territory>";
    Element element = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.loadAvailableFormats(
        metadata, element, "0", "", false /* NP not optional */);
    assertEquals("($1)", metadata.getNumberFormat(0).getNationalPrefixFormattingRule());
    assertEquals("0 $CC ($1)", metadata.getNumberFormat(0).getDomesticCarrierCodeFormattingRule());
    assertEquals("$1 $2 $3", metadata.getNumberFormat(0).getFormat());
  }

  public void testLoadAvailableFormatsSetsProvidedNationalPrefixFormattingRule()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory>"
        + "  <availableFormats>"
        + "    <numberFormat><format>$1 $2 $3</format></numberFormat>"
        + "  </availableFormats>"
        + "</territory>";
    Element element = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.loadAvailableFormats(
        metadata, element, "", "($1)", false /* NP not optional */);
    assertEquals("($1)", metadata.getNumberFormat(0).getNationalPrefixFormattingRule());
  }

  public void testLoadAvailableFormatsClearsIntlFormat()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory>"
        + "  <availableFormats>"
        + "    <numberFormat><format>$1 $2 $3</format></numberFormat>"
        + "  </availableFormats>"
        + "</territory>";
    Element element = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.loadAvailableFormats(
        metadata, element, "0", "($1)", false /* NP not optional */);
    assertEquals(0, metadata.intlNumberFormatSize());
  }

  public void testLoadAvailableFormatsHandlesMultipleNumberFormats()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory>"
        + "  <availableFormats>"
        + "    <numberFormat><format>$1 $2 $3</format></numberFormat>"
        + "    <numberFormat><format>$1-$2</format></numberFormat>"
        + "  </availableFormats>"
        + "</territory>";
    Element element = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.loadAvailableFormats(
        metadata, element, "0", "($1)", false /* NP not optional */);
    assertEquals("$1 $2 $3", metadata.getNumberFormat(0).getFormat());
    assertEquals("$1-$2", metadata.getNumberFormat(1).getFormat());
  }

  public void testLoadInternationalFormatDoesNotSetIntlFormatWhenNA()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat><intlFormat>NA</intlFormat></numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    NumberFormat.Builder nationalFormat = NumberFormat.newBuilder();
    nationalFormat.setFormat("$1 $2");

    BuildMetadataFromXml.loadInternationalFormat(metadata, numberFormatElement,
                                                 nationalFormat.build());
    assertEquals(0, metadata.intlNumberFormatSize());
  }

  // Tests setLeadingDigitsPatterns().
  public void testSetLeadingDigitsPatterns()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<numberFormat>"
        + "<leadingDigits>1</leadingDigits><leadingDigits>2</leadingDigits>"
        + "</numberFormat>";
    Element numberFormatElement = parseXmlString(xmlInput);
    NumberFormat.Builder numberFormat = NumberFormat.newBuilder();
    BuildMetadataFromXml.setLeadingDigitsPatterns(numberFormatElement, numberFormat);

    assertEquals("1", numberFormat.getLeadingDigitsPattern(0));
    assertEquals("2", numberFormat.getLeadingDigitsPattern(1));
  }

  // Tests setLeadingDigitsPatterns() in the case of international and national formatting rules
  // being present but not both defined for this numberFormat - we don't want to add them twice.
  public void testSetLeadingDigitsPatternsNotAddedTwiceWhenInternationalFormatsPresent()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<availableFormats>"
        + "  <numberFormat pattern=\"(1)(\\d{3})\">"
        + "    <leadingDigits>1</leadingDigits>"
        + "    <format>$1</format>"
        + "  </numberFormat>"
        + "  <numberFormat pattern=\"(2)(\\d{3})\">"
        + "    <leadingDigits>2</leadingDigits>"
        + "    <format>$1</format>"
        + "    <intlFormat>9-$1</intlFormat>"
        + "  </numberFormat>"
        + "</availableFormats>";
    Element element = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.loadAvailableFormats(
        metadata, element, "0", "", false /* NP not optional */);
    assertEquals(1, metadata.getNumberFormat(0).leadingDigitsPatternSize());
    assertEquals(1, metadata.getNumberFormat(1).leadingDigitsPatternSize());
    // When we merge the national format rules into the international format rules, we shouldn't add
    // the leading digit patterns multiple times.
    assertEquals(1, metadata.getIntlNumberFormat(0).leadingDigitsPatternSize());
    assertEquals(1, metadata.getIntlNumberFormat(1).leadingDigitsPatternSize());
  }

  // Tests getNationalPrefixFormattingRuleFromElement().
  public void testGetNationalPrefixFormattingRuleFromElement()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory nationalPrefixFormattingRule='$NP$FG'/>";
    Element element = parseXmlString(xmlInput);
    assertEquals("0$1",
                 BuildMetadataFromXml.getNationalPrefixFormattingRuleFromElement(element, "0"));
  }

  // Tests getDomesticCarrierCodeFormattingRuleFromElement().
  public void testGetDomesticCarrierCodeFormattingRuleFromElement()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory carrierCodeFormattingRule='$NP$CC $FG'/>";
    Element element = parseXmlString(xmlInput);
    assertEquals("0$CC $1",
                 BuildMetadataFromXml.getDomesticCarrierCodeFormattingRuleFromElement(element,
                                                                                      "0"));
  }

  // Tests processPhoneNumberDescElement().
  public void testProcessPhoneNumberDescElementWithInvalidInput()
      throws ParserConfigurationException, SAXException, IOException {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    Element territoryElement = parseXmlString("<territory/>");
    PhoneNumberDesc.Builder phoneNumberDesc;

    phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, territoryElement, "invalidType");
    assertFalse(phoneNumberDesc.hasNationalNumberPattern());
  }

  public void testProcessPhoneNumberDescElementOverridesGeneralDesc()
      throws ParserConfigurationException, SAXException, IOException {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.setNationalNumberPattern("\\d{8}");
    String xmlInput = "<territory><fixedLine>"
        + "  <nationalNumberPattern>\\d{6}</nationalNumberPattern>"
        + "</fixedLine></territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneNumberDesc.Builder phoneNumberDesc;

    phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, territoryElement, "fixedLine");
    assertEquals("\\d{6}", phoneNumberDesc.getNationalNumberPattern());
  }

  public void testBuildPhoneMetadataCollection_liteBuild() throws Exception {
    String xmlInput =
        "<phoneNumberMetadata>"
        + "  <territories>"
        + "    <territory id=\"AM\" countryCode=\"374\" internationalPrefix=\"00\">"
        + "      <generalDesc>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "      </generalDesc>"
        + "      <fixedLine>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "        <possibleLengths national=\"8\" localOnly=\"5,6\"/>"
        + "        <exampleNumber>10123456</exampleNumber>"
        + "      </fixedLine>"
        + "      <mobile>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "        <possibleLengths national=\"8\" localOnly=\"5,6\"/>"
        + "        <exampleNumber>10123456</exampleNumber>"
        + "      </mobile>"
        + "    </territory>"
        + "  </territories>"
        + "</phoneNumberMetadata>";
    Document document = parseXmlString(xmlInput).getOwnerDocument();

    PhoneMetadataCollection metadataCollection = BuildMetadataFromXml.buildPhoneMetadataCollection(
        document,
        true,  // liteBuild
        false,  // specialBuild
        false,  // isShortNumberMetadata
        false);  // isAlternateFormatsMetadata

    assertTrue(metadataCollection.getMetadataCount() == 1);
    PhoneMetadata metadata = metadataCollection.getMetadataList().get(0);
    assertTrue(metadata.hasGeneralDesc());
    assertFalse(metadata.getGeneralDesc().hasExampleNumber());
    // Some Phonemetadata.java implementations may have custom logic, so we ensure this
    // implementation is doing the right thing by checking the value of the example number even when
    // hasExampleNumber is false.
    assertEquals("", metadata.getGeneralDesc().getExampleNumber());
    assertTrue(metadata.hasFixedLine());
    assertFalse(metadata.getFixedLine().hasExampleNumber());
    assertEquals("", metadata.getFixedLine().getExampleNumber());
    assertTrue(metadata.hasMobile());
    assertFalse(metadata.getMobile().hasExampleNumber());
    assertEquals("", metadata.getMobile().getExampleNumber());
  }

  public void testBuildPhoneMetadataCollection_specialBuild() throws Exception {
    String xmlInput =
        "<phoneNumberMetadata>"
        + "  <territories>"
        + "    <territory id=\"AM\" countryCode=\"374\" internationalPrefix=\"00\">"
        + "      <generalDesc>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "      </generalDesc>"
        + "      <fixedLine>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "        <possibleLengths national=\"8\" localOnly=\"5,6\"/>"
        + "        <exampleNumber>10123456</exampleNumber>"
        + "      </fixedLine>"
        + "      <mobile>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "        <possibleLengths national=\"8\" localOnly=\"5,6\"/>"
        + "        <exampleNumber>10123456</exampleNumber>"
        + "      </mobile>"
        + "    </territory>"
        + "  </territories>"
        + "</phoneNumberMetadata>";
    Document document = parseXmlString(xmlInput).getOwnerDocument();

    PhoneMetadataCollection metadataCollection = BuildMetadataFromXml.buildPhoneMetadataCollection(
        document,
        false,  // liteBuild
        true,  // specialBuild
        false,  // isShortNumberMetadata
        false);  // isAlternateFormatsMetadata

    assertTrue(metadataCollection.getMetadataCount() == 1);
    PhoneMetadata metadata = metadataCollection.getMetadataList().get(0);
    assertTrue(metadata.hasGeneralDesc());
    assertFalse(metadata.getGeneralDesc().hasExampleNumber());
    // Some Phonemetadata.java implementations may have custom logic, so we ensure this
    // implementation is doing the right thing by checking the value of the example number even when
    // hasExampleNumber is false.
    assertEquals("", metadata.getGeneralDesc().getExampleNumber());
    // TODO: Consider clearing fixed-line if empty after being filtered.
    assertTrue(metadata.hasFixedLine());
    assertFalse(metadata.getFixedLine().hasExampleNumber());
    assertEquals("", metadata.getFixedLine().getExampleNumber());
    assertTrue(metadata.hasMobile());
    assertTrue(metadata.getMobile().hasExampleNumber());
    assertEquals("10123456", metadata.getMobile().getExampleNumber());
  }

  public void testBuildPhoneMetadataCollection_fullBuild() throws Exception {
    String xmlInput =
        "<phoneNumberMetadata>"
        + "  <territories>"
        + "    <territory id=\"AM\" countryCode=\"374\" internationalPrefix=\"00\">"
        + "      <generalDesc>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "      </generalDesc>"
        + "      <fixedLine>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "        <possibleLengths national=\"8\" localOnly=\"5,6\"/>"
        + "        <exampleNumber>10123456</exampleNumber>"
        + "      </fixedLine>"
        + "      <mobile>"
        + "        <nationalNumberPattern>[1-9]\\d{7}</nationalNumberPattern>"
        + "        <possibleLengths national=\"8\" localOnly=\"5,6\"/>"
        + "        <exampleNumber>10123456</exampleNumber>"
        + "      </mobile>"
        + "    </territory>"
        + "  </territories>"
        + "</phoneNumberMetadata>";
    Document document = parseXmlString(xmlInput).getOwnerDocument();

    PhoneMetadataCollection metadataCollection = BuildMetadataFromXml.buildPhoneMetadataCollection(
        document,
        false,  // liteBuild
        false,  // specialBuild
        false,  // isShortNumberMetadata
        false);  // isAlternateFormatsMetadata

    assertTrue(metadataCollection.getMetadataCount() == 1);
    PhoneMetadata metadata = metadataCollection.getMetadataList().get(0);
    assertTrue(metadata.hasGeneralDesc());
    assertFalse(metadata.getGeneralDesc().hasExampleNumber());
    // Some Phonemetadata.java implementations may have custom logic, so we ensure this
    // implementation is doing the right thing by checking the value of the example number even when
    // hasExampleNumber is false.
    assertEquals("", metadata.getGeneralDesc().getExampleNumber());
    assertTrue(metadata.hasFixedLine());
    assertTrue(metadata.getFixedLine().hasExampleNumber());
    assertEquals("10123456", metadata.getFixedLine().getExampleNumber());
    assertTrue(metadata.hasMobile());
    assertTrue(metadata.getMobile().hasExampleNumber());
    assertEquals("10123456", metadata.getMobile().getExampleNumber());
  }

  public void testProcessPhoneNumberDescOutputsExampleNumberByDefault()
      throws ParserConfigurationException, SAXException, IOException {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    String xmlInput = "<territory><fixedLine>"
        + "  <exampleNumber>01 01 01 01</exampleNumber>"
        + "</fixedLine></territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneNumberDesc.Builder phoneNumberDesc;

    phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, territoryElement, "fixedLine");
    assertEquals("01 01 01 01", phoneNumberDesc.getExampleNumber());
  }

  public void testProcessPhoneNumberDescRemovesWhiteSpacesInPatterns()
      throws ParserConfigurationException, SAXException, IOException {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    String xmlInput = "<territory><fixedLine>"
        + "  <nationalNumberPattern>\t \\d { 6 } </nationalNumberPattern>"
        + "</fixedLine></territory>";
    Element countryElement = parseXmlString(xmlInput);
    PhoneNumberDesc.Builder phoneNumberDesc;

    phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, countryElement, "fixedLine");
    assertEquals("\\d{6}", phoneNumberDesc.getNationalNumberPattern());
  }

  // Tests setRelevantDescPatterns().
  public void testSetRelevantDescPatternsSetsSameMobileAndFixedLinePattern()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode=\"33\">"
        + "  <fixedLine><nationalNumberPattern>\\d{6}</nationalNumberPattern></fixedLine>"
        + "  <mobile><nationalNumberPattern>\\d{6}</nationalNumberPattern></mobile>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    // Should set sameMobileAndFixedPattern to true.
    BuildMetadataFromXml.setRelevantDescPatterns(metadata, territoryElement,
        false /* isShortNumberMetadata */);
    assertTrue(metadata.getSameMobileAndFixedLinePattern());
  }

  public void testSetRelevantDescPatternsSetsAllDescriptionsForRegularLengthNumbers()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode=\"33\">"
        + "  <fixedLine><nationalNumberPattern>\\d{1}</nationalNumberPattern></fixedLine>"
        + "  <mobile><nationalNumberPattern>\\d{2}</nationalNumberPattern></mobile>"
        + "  <pager><nationalNumberPattern>\\d{3}</nationalNumberPattern></pager>"
        + "  <tollFree><nationalNumberPattern>\\d{4}</nationalNumberPattern></tollFree>"
        + "  <premiumRate><nationalNumberPattern>\\d{5}</nationalNumberPattern></premiumRate>"
        + "  <sharedCost><nationalNumberPattern>\\d{6}</nationalNumberPattern></sharedCost>"
        + "  <personalNumber><nationalNumberPattern>\\d{7}</nationalNumberPattern></personalNumber>"
        + "  <voip><nationalNumberPattern>\\d{8}</nationalNumberPattern></voip>"
        + "  <uan><nationalNumberPattern>\\d{9}</nationalNumberPattern></uan>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.setRelevantDescPatterns(metadata, territoryElement,
        false /* isShortNumberMetadata */);
    assertEquals("\\d{1}", metadata.getFixedLine().getNationalNumberPattern());
    assertEquals("\\d{2}", metadata.getMobile().getNationalNumberPattern());
    assertEquals("\\d{3}", metadata.getPager().getNationalNumberPattern());
    assertEquals("\\d{4}", metadata.getTollFree().getNationalNumberPattern());
    assertEquals("\\d{5}", metadata.getPremiumRate().getNationalNumberPattern());
    assertEquals("\\d{6}", metadata.getSharedCost().getNationalNumberPattern());
    assertEquals("\\d{7}", metadata.getPersonalNumber().getNationalNumberPattern());
    assertEquals("\\d{8}", metadata.getVoip().getNationalNumberPattern());
    assertEquals("\\d{9}", metadata.getUan().getNationalNumberPattern());
  }

  public void testSetRelevantDescPatternsSetsAllDescriptionsForShortNumbers()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory ID=\"FR\">"
        + "  <tollFree><nationalNumberPattern>\\d{1}</nationalNumberPattern></tollFree>"
        + "  <standardRate><nationalNumberPattern>\\d{2}</nationalNumberPattern></standardRate>"
        + "  <premiumRate><nationalNumberPattern>\\d{3}</nationalNumberPattern></premiumRate>"
        + "  <shortCode><nationalNumberPattern>\\d{4}</nationalNumberPattern></shortCode>"
        + "  <carrierSpecific>"
        + "    <nationalNumberPattern>\\d{5}</nationalNumberPattern>"
        + "  </carrierSpecific>"
        + "  <smsServices>"
        + "    <nationalNumberPattern>\\d{6}</nationalNumberPattern>"
        + "  </smsServices>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    BuildMetadataFromXml.setRelevantDescPatterns(metadata, territoryElement,
        true /* isShortNumberMetadata */);
    assertEquals("\\d{1}", metadata.getTollFree().getNationalNumberPattern());
    assertEquals("\\d{2}", metadata.getStandardRate().getNationalNumberPattern());
    assertEquals("\\d{3}", metadata.getPremiumRate().getNationalNumberPattern());
    assertEquals("\\d{4}", metadata.getShortCode().getNationalNumberPattern());
    assertEquals("\\d{5}", metadata.getCarrierSpecific().getNationalNumberPattern());
    assertEquals("\\d{6}", metadata.getSmsServices().getNationalNumberPattern());
  }

  public void testSetRelevantDescPatternsThrowsErrorIfTypePresentMultipleTimes()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode=\"33\">"
        + "  <fixedLine><nationalNumberPattern>\\d{6}</nationalNumberPattern></fixedLine>"
        + "  <fixedLine><nationalNumberPattern>\\d{6}</nationalNumberPattern></fixedLine>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
    try {
      BuildMetadataFromXml.setRelevantDescPatterns(metadata, territoryElement,
          false /* isShortNumberMetadata */);
      fail("Fixed-line info present twice for France: we should fail.");
    } catch (RuntimeException expected) {
      assertEquals("Multiple elements with type fixedLine found.", expected.getMessage());
    }
  }

  public void testAlternateFormatsOmitsDescPatterns()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode=\"33\">"
        + "  <availableFormats>"
        + "    <numberFormat pattern=\"(1)(\\d{3})\">"
        + "      <leadingDigits>1</leadingDigits>"
        + "      <format>$1</format>"
        + "    </numberFormat>"
        + "  </availableFormats>"
        + "  <fixedLine><nationalNumberPattern>\\d{1}</nationalNumberPattern></fixedLine>"
        + "  <shortCode><nationalNumberPattern>\\d{2}</nationalNumberPattern></shortCode>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata metadata = BuildMetadataFromXml.loadCountryMetadata("FR", territoryElement,
        false /* isShortNumberMetadata */, true /* isAlternateFormatsMetadata */).build();
    assertEquals("(1)(\\d{3})", metadata.getNumberFormat(0).getPattern());
    assertEquals("1", metadata.getNumberFormat(0).getLeadingDigitsPattern(0));
    assertEquals("$1", metadata.getNumberFormat(0).getFormat());
    assertFalse(metadata.hasFixedLine());
    assertNull(metadata.getFixedLine());
    assertFalse(metadata.hasShortCode());
    assertNull(metadata.getShortCode());
  }

  public void testNationalPrefixRulesSetCorrectly()
      throws ParserConfigurationException, SAXException, IOException {
    String xmlInput = "<territory countryCode=\"33\" nationalPrefix=\"0\""
        + " nationalPrefixFormattingRule=\"$NP$FG\">"
        + "  <availableFormats>"
        + "    <numberFormat pattern=\"(1)(\\d{3})\" nationalPrefixOptionalWhenFormatting=\"true\">"
        + "      <leadingDigits>1</leadingDigits>"
        + "      <format>$1</format>"
        + "    </numberFormat>"
        + "    <numberFormat pattern=\"(\\d{3})\" nationalPrefixOptionalWhenFormatting=\"false\">"
        + "      <leadingDigits>2</leadingDigits>"
        + "      <format>$1</format>"
        + "    </numberFormat>"
        + "  </availableFormats>"
        + "  <fixedLine><nationalNumberPattern>\\d{1}</nationalNumberPattern></fixedLine>"
        + "</territory>";
    Element territoryElement = parseXmlString(xmlInput);
    PhoneMetadata metadata = BuildMetadataFromXml.loadCountryMetadata("FR", territoryElement,
        false /* isShortNumberMetadata */, true /* isAlternateFormatsMetadata */).build();
    assertTrue(metadata.getNumberFormat(0).getNationalPrefixOptionalWhenFormatting());
    // This is inherited from the territory, with $NP replaced by the actual national prefix, and
    // $FG replaced with $1.
    assertEquals("0$1", metadata.getNumberFormat(0).getNationalPrefixFormattingRule());
    // Here it is explicitly set to false.
    assertFalse(metadata.getNumberFormat(1).getNationalPrefixOptionalWhenFormatting());
  }

  public void testProcessPhoneNumberDescElement_PossibleLengthsSetCorrectly() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    // The number lengths set for the general description must be a super-set of those in the
    // element being parsed.
    generalDesc.addPossibleLength(4);
    generalDesc.addPossibleLength(6);
    generalDesc.addPossibleLength(7);
    generalDesc.addPossibleLength(13);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        // Sorting will be done when parsing.
        + "  <possibleLengths national=\"13,4\" localOnly=\"6\"/>"
        + "</fixedLine>"
        + "</territory>");

    PhoneNumberDesc.Builder fixedLine;
    PhoneNumberDesc.Builder mobile;

    fixedLine = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, territoryElement, "fixedLine");
    mobile = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, territoryElement, "mobile");

    assertEquals(2, fixedLine.getPossibleLengthCount());
    assertEquals(4, fixedLine.getPossibleLength(0));
    assertEquals(13, fixedLine.getPossibleLength(1));
    assertEquals(1, fixedLine.getPossibleLengthLocalOnlyCount());

    // We use [-1] to denote that there are no possible lengths; we don't leave it empty, since for
    // compression reasons, we use the empty list to mean that the generalDesc possible lengths
    // apply.
    assertEquals(1, mobile.getPossibleLengthCount());
    assertEquals(-1, mobile.getPossibleLength(0));
    assertEquals(0, mobile.getPossibleLengthLocalOnlyCount());
  }

  public void testSetPossibleLengthsGeneralDesc_BuiltFromChildElements() throws Exception {
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"13\" localOnly=\"6\"/>"
        + "</fixedLine>"
        + "<mobile>"
        + "  <possibleLengths national=\"15\" localOnly=\"7,13\"/>"
        + "</mobile>"
        + "<tollFree>"
        + "  <possibleLengths national=\"15\"/>"
        + "</tollFree>"
        + "</territory>");
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    BuildMetadataFromXml.setPossibleLengthsGeneralDesc(
        generalDesc, "someId", territoryElement, false /* not short-number metadata */);

    assertEquals(2, generalDesc.getPossibleLengthCount());
    assertEquals(13, generalDesc.getPossibleLength(0));
    // 15 is present twice in the input in different sections, but only once in the output.
    assertEquals(15, generalDesc.getPossibleLength(1));
    assertEquals(2, generalDesc.getPossibleLengthLocalOnlyCount());
    assertEquals(6, generalDesc.getPossibleLengthLocalOnly(0));
    assertEquals(7, generalDesc.getPossibleLengthLocalOnly(1));
    // 13 is skipped as a "local only" length, since it is also present as a normal length.
  }

  public void testSetPossibleLengthsGeneralDesc_IgnoresNoIntlDialling() throws Exception {
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"13\"/>"
        + "</fixedLine>"
        + "<noInternationalDialling>"
        + "  <possibleLengths national=\"15\"/>"
        + "</noInternationalDialling>"
        + "</territory>");
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    BuildMetadataFromXml.setPossibleLengthsGeneralDesc(
        generalDesc, "someId", territoryElement, false /* not short-number metadata */);

    assertEquals(1, generalDesc.getPossibleLengthCount());
    assertEquals(13, generalDesc.getPossibleLength(0));
    // 15 is skipped because noInternationalDialling should not contribute to the general lengths;
    // it isn't a particular "type" of number per se, it is a property that different types may
    // have.
  }

  public void testSetPossibleLengthsGeneralDesc_ShortNumberMetadata() throws Exception {
    Element territoryElement = parseXmlString("<territory>"
        + "<shortCode>"
        + "  <possibleLengths national=\"6,13\"/>"
        + "</shortCode>"
        + "<carrierSpecific>"
        + "  <possibleLengths national=\"7,13,15\"/>"
        + "</carrierSpecific>"
        + "<tollFree>"
        + "  <possibleLengths national=\"15\"/>"
        + "</tollFree>"
        + "<smsServices>"
        + "  <possibleLengths national=\"5\"/>"
        + "</smsServices>"
        + "</territory>");
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    BuildMetadataFromXml.setPossibleLengthsGeneralDesc(
        generalDesc, "someId", territoryElement, true /* short-number metadata */);

    // All elements other than shortCode are ignored when creating the general desc.
    assertEquals(2, generalDesc.getPossibleLengthCount());
    assertEquals(6, generalDesc.getPossibleLength(0));
    assertEquals(13, generalDesc.getPossibleLength(1));
  }

  public void testSetPossibleLengthsGeneralDesc_ShortNumberMetadataErrorsOnLocalLengths()
      throws Exception {
    Element territoryElement = parseXmlString("<territory>"
        + "<shortCode>"
        + "  <possibleLengths national=\"13\" localOnly=\"6\"/>"
        + "</shortCode>"
        + "</territory>");

    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    try {
      BuildMetadataFromXml.setPossibleLengthsGeneralDesc(
          generalDesc, "someId", territoryElement, true /* short-number metadata */);
      fail();
    } catch (RuntimeException expected) {
      // This should be an error, localOnly is not permitted in short-code metadata.
      assertEquals("Found local-only lengths in short-number metadata", expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorDuplicates() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(6);

    Element territoryElement = parseXmlString("<territory>"
        + "<mobile>"
        + "  <possibleLengths national=\"6,6\"/>"
        + "</mobile>"
        + "</territory>");

    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(generalDesc, territoryElement, "mobile");
      fail("Invalid data seen: expected failure.");
    } catch (RuntimeException expected) {
      // This should be an error, 6 is seen twice.
      assertEquals("Duplicate length element found (6) in possibleLength string 6,6",
          expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorDuplicatesOneLocal() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(6);

    Element territoryElement = parseXmlString("<territory>"
        + "<mobile>"
        + "  <possibleLengths national=\"6\" localOnly=\"6\"/>"
        + "</mobile>"
        + "</territory>");

    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(generalDesc, territoryElement, "mobile");
      fail("Invalid data seen: expected failure.");
    } catch (RuntimeException expected) {
      // This should be an error, 6 is seen twice.
      assertEquals("Possible length(s) found specified as a normal and local-only length: [6]",
          expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorUncoveredLengths() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<noInternationalDialling>"
        // Sorting will be done when parsing.
        + "  <possibleLengths national=\"6,7,4\"/>"
        + "</noInternationalDialling>"
        + "</territory>");
    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "noInternationalDialling");
      fail("Lengths present not covered by the general desc: should fail.");
    } catch (RuntimeException expected) {
      // Lengths were present that the general description didn't know about.
      assertTrue(expected.getMessage().contains("Out-of-range possible length"));
    }
  }

  public void testProcessPhoneNumberDescElement_SameAsParent() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    // The number lengths set for the general description must be a super-set of those in the
    // element being parsed.
    generalDesc.addPossibleLength(4);
    generalDesc.addPossibleLength(6);
    generalDesc.addPossibleLength(7);
    generalDesc.addPossibleLengthLocalOnly(2);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        // Sorting will be done when parsing.
        + "  <possibleLengths national=\"6,7,4\" localOnly=\"2\"/>"
        + "</fixedLine>"
        + "</territory>");

    PhoneNumberDesc.Builder phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
        generalDesc, territoryElement, "fixedLine");
    // No possible lengths should be present, because they match the general description.
    assertEquals(0, phoneNumberDesc.getPossibleLengthCount());
    // Local-only lengths should be present for child elements such as fixed-line.
    assertEquals(1, phoneNumberDesc.getPossibleLengthLocalOnlyCount());
  }

  public void testProcessPhoneNumberDescElement_InvalidNumber() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"4d\"/>"
        + "</fixedLine>"
        + "</territory>");

    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "fixedLine");
      fail("4d is not a number.");
    } catch (NumberFormatException expected) {
      assertEquals("For input string: \"4d\"", expected.getMessage());
    }
  }

  public void testLoadCountryMetadata_GeneralDescHasNumberLengthsSet() throws Exception {
    Element territoryElement = parseXmlString("<territory>"
        + "<generalDesc>"
        // This shouldn't be set, the possible lengths should be derived for generalDesc.
        + "  <possibleLengths national=\"4\"/>"
        + "</generalDesc>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"4\"/>"
        + "</fixedLine>"
        + "</territory>");

    try {
      BuildMetadataFromXml.loadCountryMetadata("FR", territoryElement,
          false /* isShortNumberMetadata */, false /* isAlternateFormatsMetadata */);
      fail("Possible lengths explicitly set for generalDesc and should not be: we should fail.");
    } catch (RuntimeException expected) {
      assertEquals("Found possible lengths specified at general desc: this should be derived"
          + " from child elements. Affected country: FR", expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorEmptyPossibleLengthStringAttribute()
      throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"\"/>"
        + "</fixedLine>"
        + "</territory>");
    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "fixedLine");
      fail("Empty possible length string.");
    } catch (RuntimeException expected) {
      assertEquals("Empty possibleLength string found.", expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorRangeSpecifiedWithComma()
      throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"[4,7]\"/>"
        + "</fixedLine>"
        + "</territory>");
    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "fixedLine");
      fail("Ranges shouldn't use a comma.");
    } catch (RuntimeException expected) {
      assertEquals("Missing end of range character in possible length string [4,7].",
          expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorIncompleteRange() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"[4-\"/>"
        + "</fixedLine>"
        + "</territory>");

    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "fixedLine");
      fail("Should fail: range incomplete.");
    } catch (RuntimeException expected) {
      assertEquals("Missing end of range character in possible length string [4-.",
          expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorNoDashInRange() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"[4:10]\"/>"
        + "</fixedLine>"
        + "</territory>");

    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "fixedLine");
      fail("Should fail: range incomplete.");
    } catch (RuntimeException expected) {
      assertEquals("Ranges must have exactly one - character: missing for [4:10].",
          expected.getMessage());
    }
  }

  public void testProcessPhoneNumberDescElement_ErrorRangeIsNotFromMinToMax() throws Exception {
    PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
    generalDesc.addPossibleLength(4);
    Element territoryElement = parseXmlString("<territory>"
        + "<fixedLine>"
        + "  <possibleLengths national=\"[10-10]\"/>"
        + "</fixedLine>"
        + "</territory>");

    try {
      BuildMetadataFromXml.processPhoneNumberDescElement(
          generalDesc, territoryElement, "fixedLine");
      fail("Should fail: range even.");
    } catch (RuntimeException expected) {
      assertEquals("The first number in a range should be two or more digits lower than the second."
          + " Culprit possibleLength string: [10-10]", expected.getMessage());
    }
  }

  public void testGetMetadataFilter() {
    assertEquals(BuildMetadataFromXml.getMetadataFilter(false, false),
        MetadataFilter.emptyFilter());
    assertEquals(BuildMetadataFromXml.getMetadataFilter(true, false),
        MetadataFilter.forLiteBuild());
    assertEquals(BuildMetadataFromXml.getMetadataFilter(false, true),
        MetadataFilter.forSpecialBuild());
    try {
      BuildMetadataFromXml.getMetadataFilter(true, true);
      fail("getMetadataFilter should fail when liteBuild and specialBuild are both set");
    } catch (RuntimeException e) {
      // Test passed.
    }
  }
}
