localOnlyLengths, PhoneNumberDesc parentDesc, PhoneNumberDesc.Builder desc) {
+ // We clear these fields since the metadata tends to inherit from the parent element for other
+ // fields (via a mergeFrom).
+ desc.clearPossibleLength();
+ desc.clearPossibleLengthLocalOnly();
+ // Only add the lengths to this sub-type if they aren't exactly the same as the possible
+ // lengths in the general desc (for metadata size reasons).
+ if (parentDesc == null || !arePossibleLengthsEqual(lengths, parentDesc)) {
+ for (Integer length : lengths) {
+ if (parentDesc == null || parentDesc.getPossibleLengthList().contains(length)) {
+ desc.addPossibleLength(length);
+ } else {
+ // We shouldn't have possible lengths defined in a child element that are not covered by
+ // the general description. We check this here even though the general description is
+ // derived from child elements because it is only derived from a subset, and we need to
+ // ensure *all* child elements have a valid possible length.
+ throw new RuntimeException(String.format(
+ "Out-of-range possible length found (%d), parent lengths %s.",
+ length, parentDesc.getPossibleLengthList()));
+ }
+ }
+ }
+ // We check that the local-only length isn't also a normal possible length (only relevant for
+ // the general-desc, since within elements such as fixed-line we would throw an exception if we
+ // saw this) before adding it to the collection of possible local-only lengths.
+ for (Integer length : localOnlyLengths) {
+ if (!lengths.contains(length)) {
+ // We check it is covered by either of the possible length sets of the parent
+ // PhoneNumberDesc, because for example 7 might be a valid localOnly length for mobile, but
+ // a valid national length for fixedLine, so the generalDesc would have the 7 removed from
+ // localOnly.
+ if (parentDesc == null || parentDesc.getPossibleLengthLocalOnlyList().contains(length)
+ || parentDesc.getPossibleLengthList().contains(length)) {
+ desc.addPossibleLengthLocalOnly(length);
+ } else {
+ throw new RuntimeException(String.format(
+ "Out-of-range local-only possible length found (%d), parent length %s.",
+ length, parentDesc.getPossibleLengthLocalOnlyList()));
+ }
+ }
+ }
+ }
+
+ // @VisibleForTesting
+ static PhoneMetadata.Builder loadCountryMetadata(String regionCode,
+ Element element,
+ boolean isShortNumberMetadata,
+ boolean isAlternateFormatsMetadata) {
+ String nationalPrefix = getNationalPrefix(element);
+ PhoneMetadata.Builder metadata = loadTerritoryTagMetadata(regionCode, element, nationalPrefix);
+ String nationalPrefixFormattingRule =
+ getNationalPrefixFormattingRuleFromElement(element, nationalPrefix);
+ loadAvailableFormats(metadata, element, nationalPrefix,
+ nationalPrefixFormattingRule,
+ element.hasAttribute(NATIONAL_PREFIX_OPTIONAL_WHEN_FORMATTING));
+ if (!isAlternateFormatsMetadata) {
+ // The alternate formats metadata does not need most of the patterns to be set.
+ setRelevantDescPatterns(metadata, element, isShortNumberMetadata);
+ }
+ return metadata;
+ }
+
+ /**
+ * Processes the custom build flags and gets a {@code MetadataFilter} which may be used to
+ * filter {@code PhoneMetadata} objects. Incompatible flag combinations throw RuntimeException.
+ *
+ * @param liteBuild The liteBuild flag value as given by the command-line
+ * @param specialBuild The specialBuild flag value as given by the command-line
+ */
+ // @VisibleForTesting
+ static MetadataFilter getMetadataFilter(boolean liteBuild, boolean specialBuild) {
+ if (specialBuild) {
+ if (liteBuild) {
+ throw new RuntimeException("liteBuild and specialBuild may not both be set");
+ }
+ return MetadataFilter.forSpecialBuild();
+ }
+ if (liteBuild) {
+ return MetadataFilter.forLiteBuild();
+ }
+ return MetadataFilter.emptyFilter();
+ }
+}
diff --git a/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/Command.java b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/Command.java
new file mode 100644
index 0000000000..b54ef73398
--- /dev/null
+++ b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/Command.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Abstract class defining a common interface for commands provided by build tools (e.g: commands to
+ * generate code or to download source files).
+ *
+ * Subclass it to create a new command (e.g: code generation step).
+ *
+ * @author Philippe Liard
+ */
+public abstract class Command {
+ // The arguments provided to this command. The first one is the name of the command.
+ private String[] args;
+
+ /**
+ * Entry point of the command called by the CommandDispatcher when requested. This method must be
+ * implemented by subclasses.
+ */
+ public abstract boolean start();
+
+ /**
+ * The name of the command is used by the CommandDispatcher to execute the requested command. The
+ * Dispatcher will pass along all command-line arguments to this command, so args[0] will be
+ * always the command name.
+ */
+ public abstract String getCommandName();
+
+ public String[] getArgs() {
+ return args;
+ }
+
+ public void setArgs(String[] args) {
+ this.args = args;
+ }
+}
diff --git a/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/CommandDispatcher.java b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/CommandDispatcher.java
new file mode 100644
index 0000000000..2806ba3ea5
--- /dev/null
+++ b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/CommandDispatcher.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+/**
+ * This class is designed to execute a requested command among a set of provided commands.
+ * The dispatching is performed according to the requested command name, which is provided as the
+ * first string of the 'args' array. The 'args' array also contains the command arguments available
+ * from position 1 to end. The verification of the arguments' consistency is under the
+ * responsibility of the command since the dispatcher can't be aware of its underlying goals.
+ *
+ * @see Command
+ * @author Philippe Liard
+ */
+public class CommandDispatcher {
+ // Command line arguments passed to the command which will be executed. Note that the first one is
+ // the name of the command.
+ private final String[] args;
+ // Supported commands by this dispatcher.
+ private final Command[] commands;
+
+ public CommandDispatcher(String[] args, Command[] commands) {
+ this.args = args;
+ this.commands = commands;
+ }
+
+ /**
+ * Executes the command named `args[0]` if any. If the requested command (in args[0]) is not
+ * supported, display a help message.
+ *
+ *
Note that the command name comparison is case sensitive.
+ */
+ public boolean start() {
+ if (args.length != 0) {
+ String requestedCommand = args[0];
+
+ for (Command command : commands) {
+ if (command.getCommandName().equals(requestedCommand)) {
+ command.setArgs(args);
+ return command.start();
+ }
+ }
+ }
+ displayUsage();
+ return false;
+ }
+
+ /**
+ * Displays a message containing the list of the supported commands by this dispatcher.
+ */
+ private void displayUsage() {
+ StringBuilder msg = new StringBuilder("Usage: java -jar /path/to/jar [ ");
+ int i = 0;
+
+ for (Command command : commands) {
+ msg.append(command.getCommandName());
+ if (i++ != commands.length - 1) {
+ msg.append(" | ");
+ }
+ }
+ msg.append(" ] args");
+ System.err.println(msg.toString());
+ }
+}
diff --git a/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/CopyrightNotice.java b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/CopyrightNotice.java
new file mode 100644
index 0000000000..d8cd9e2f1a
--- /dev/null
+++ b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/CopyrightNotice.java
@@ -0,0 +1,66 @@
+/*
+ * 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 java.io.IOException;
+import java.io.Writer;
+import java.util.Formatter;
+
+/**
+ * Class containing the Apache copyright notice used by code generators.
+ *
+ * @author Philippe Liard
+ */
+public class CopyrightNotice {
+
+ private static final String TEXT_OPENING =
+ "/*\n";
+
+ private static final String TEXT_OPENING_FOR_JAVASCRIPT =
+ "/**\n" +
+ " * @license\n";
+
+ private static final String TEXT =
+ " * Copyright (C) %d The Libphonenumber Authors\n" +
+ " *\n" +
+ " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
+ " * you may not use this file except in compliance with the License.\n" +
+ " * You may obtain a copy of the License at\n" +
+ " *\n" +
+ " * http://www.apache.org/licenses/LICENSE-2.0\n" +
+ " *\n" +
+ " * Unless required by applicable law or agreed to in writing, software\n" +
+ " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
+ " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
+ " * See the License for the specific language governing permissions and\n" +
+ " * limitations under the License.\n" +
+ " */\n\n";
+
+ static final void writeTo(Writer writer, int year) throws IOException {
+ writeTo(writer, year, false);
+ }
+
+ static final void writeTo(Writer writer, int year, boolean isJavascript) throws IOException {
+ if (isJavascript) {
+ writer.write(TEXT_OPENING_FOR_JAVASCRIPT);
+ } else {
+ writer.write(TEXT_OPENING);
+ }
+ Formatter formatter = new Formatter(writer);
+ formatter.format(TEXT, year);
+ }
+}
diff --git a/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/FileUtils.java b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/FileUtils.java
new file mode 100644
index 0000000000..41a11eedd1
--- /dev/null
+++ b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/FileUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ * under the License.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Helper class containing methods designed to ease file manipulation and generation.
+ *
+ * @author Philippe Liard
+ */
+public class FileUtils {
+ /**
+ * Silently closes a resource (i.e: don't throw any exception).
+ */
+ private static void close(Closeable closeable) {
+ if (closeable == null) {
+ return;
+ }
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ /**
+ * Silently closes multiple resources. This method doesn't throw any exception when an error
+ * occurs when a resource is being closed.
+ */
+ public static void closeFiles(Closeable ... closeables) {
+ for (Closeable closeable : closeables) {
+ close(closeable);
+ }
+ }
+}
diff --git a/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/MetadataFilter.java b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/MetadataFilter.java
new file mode 100644
index 0000000000..a783025f33
--- /dev/null
+++ b/tools/java/common/target/test-classes/com/google/i18n/phonenumbers/MetadataFilter.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2016 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.PhoneMetadata;
+import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
+import java.util.Arrays;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Class to encapsulate the metadata filtering logic and restrict visibility into raw data
+ * structures.
+ *
+ *
+ * WARNING: This is an internal API which is under development and subject to backwards-incompatible
+ * changes without notice. Any changes are not guaranteed to be reflected in the versioning scheme
+ * of the public API, nor in release notes.
+ */
+final class MetadataFilter {
+ // The following 3 sets comprise all the PhoneMetadata fields as defined at phonemetadata.proto
+ // which may be excluded from customized serializations of the binary metadata. Fields that are
+ // core to the library functionality may not be listed here.
+ // excludableParentFields are PhoneMetadata fields of type PhoneNumberDesc.
+ // excludableChildFields are PhoneNumberDesc fields of primitive type.
+ // excludableChildlessFields are PhoneMetadata fields of primitive type.
+ // Currently we support only one non-primitive type and the depth of the "family tree" is 2,
+ // meaning a field may have only direct descendants, who may not have descendants of their own. If
+ // this changes, the blacklist handling in this class should also change.
+ // @VisibleForTesting
+ static final TreeSet excludableParentFields = new TreeSet(Arrays.asList(
+ "fixedLine",
+ "mobile",
+ "tollFree",
+ "premiumRate",
+ "sharedCost",
+ "personalNumber",
+ "voip",
+ "pager",
+ "uan",
+ "emergency",
+ "voicemail",
+ "shortCode",
+ "standardRate",
+ "carrierSpecific",
+ "smsServices",
+ "noInternationalDialling"));
+
+ // Note: If this set changes, the descHasData implementation must change in PhoneNumberUtil.
+ // The current implementation assumes that all PhoneNumberDesc fields are present here, since it
+ // "clears" a PhoneNumberDesc field by simply clearing all of the fields under it. See the comment
+ // above, about all 3 sets, for more about these fields.
+ // @VisibleForTesting
+ static final TreeSet excludableChildFields = new TreeSet(Arrays.asList(
+ "nationalNumberPattern",
+ "possibleLength",
+ "possibleLengthLocalOnly",
+ "exampleNumber"));
+
+ // @VisibleForTesting
+ static final TreeSet excludableChildlessFields = new TreeSet(Arrays.asList(
+ "preferredInternationalPrefix",
+ "nationalPrefix",
+ "preferredExtnPrefix",
+ "nationalPrefixTransformRule",
+ "sameMobileAndFixedLinePattern",
+ "mainCountryForCode",
+ "mobileNumberPortableRegion"));
+
+ private final TreeMap> blacklist;
+
+ // Note: If changing the blacklist here or the name of the method, update documentation about
+ // affected methods at the same time:
+ // https://github.com/google/libphonenumber/blob/master/FAQ.md#what-is-the-metadatalitejsmetadata_lite-option
+ static MetadataFilter forLiteBuild() {
+ // "exampleNumber" is a blacklist.
+ return new MetadataFilter(parseFieldMapFromString("exampleNumber"));
+ }
+
+ static MetadataFilter forSpecialBuild() {
+ // "mobile" is a whitelist.
+ return new MetadataFilter(computeComplement(parseFieldMapFromString("mobile")));
+ }
+
+ static MetadataFilter emptyFilter() {
+ // Empty blacklist, meaning we filter nothing.
+ return new MetadataFilter(new TreeMap>());
+ }
+
+ // @VisibleForTesting
+ MetadataFilter(TreeMap> blacklist) {
+ this.blacklist = blacklist;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return blacklist.equals(((MetadataFilter) obj).blacklist);
+ }
+
+ @Override
+ public int hashCode() {
+ return blacklist.hashCode();
+ }
+
+ /**
+ * Clears certain fields in {@code metadata} as defined by the {@code MetadataFilter} instance.
+ * Note that this changes the mutable {@code metadata} object, and is not thread-safe. If this
+ * method does not return successfully, do not assume {@code metadata} has not changed.
+ *
+ * @param metadata The {@code PhoneMetadata} object to be filtered
+ */
+ void filterMetadata(PhoneMetadata.Builder metadata) {
+ // TODO: Consider clearing if the filtered PhoneNumberDesc is empty.
+ if (metadata.hasFixedLine()) {
+ metadata.setFixedLine(getFiltered("fixedLine", metadata.getFixedLine()));
+ }
+ if (metadata.hasMobile()) {
+ metadata.setMobile(getFiltered("mobile", metadata.getMobile()));
+ }
+ if (metadata.hasTollFree()) {
+ metadata.setTollFree(getFiltered("tollFree", metadata.getTollFree()));
+ }
+ if (metadata.hasPremiumRate()) {
+ metadata.setPremiumRate(getFiltered("premiumRate", metadata.getPremiumRate()));
+ }
+ if (metadata.hasSharedCost()) {
+ metadata.setSharedCost(getFiltered("sharedCost", metadata.getSharedCost()));
+ }
+ if (metadata.hasPersonalNumber()) {
+ metadata.setPersonalNumber(getFiltered("personalNumber", metadata.getPersonalNumber()));
+ }
+ if (metadata.hasVoip()) {
+ metadata.setVoip(getFiltered("voip", metadata.getVoip()));
+ }
+ if (metadata.hasPager()) {
+ metadata.setPager(getFiltered("pager", metadata.getPager()));
+ }
+ if (metadata.hasUan()) {
+ metadata.setUan(getFiltered("uan", metadata.getUan()));
+ }
+ if (metadata.hasEmergency()) {
+ metadata.setEmergency(getFiltered("emergency", metadata.getEmergency()));
+ }
+ if (metadata.hasVoicemail()) {
+ metadata.setVoicemail(getFiltered("voicemail", metadata.getVoicemail()));
+ }
+ if (metadata.hasShortCode()) {
+ metadata.setShortCode(getFiltered("shortCode", metadata.getShortCode()));
+ }
+ if (metadata.hasStandardRate()) {
+ metadata.setStandardRate(getFiltered("standardRate", metadata.getStandardRate()));
+ }
+ if (metadata.hasCarrierSpecific()) {
+ metadata.setCarrierSpecific(getFiltered("carrierSpecific", metadata.getCarrierSpecific()));
+ }
+ if (metadata.hasSmsServices()) {
+ metadata.setSmsServices(getFiltered("smsServices", metadata.getSmsServices()));
+ }
+ if (metadata.hasNoInternationalDialling()) {
+ metadata.setNoInternationalDialling(getFiltered("noInternationalDialling",
+ metadata.getNoInternationalDialling()));
+ }
+
+ if (shouldDrop("preferredInternationalPrefix")) {
+ metadata.clearPreferredInternationalPrefix();
+ }
+ if (shouldDrop("nationalPrefix")) {
+ metadata.clearNationalPrefix();
+ }
+ if (shouldDrop("preferredExtnPrefix")) {
+ metadata.clearPreferredExtnPrefix();
+ }
+ if (shouldDrop("nationalPrefixTransformRule")) {
+ metadata.clearNationalPrefixTransformRule();
+ }
+ if (shouldDrop("sameMobileAndFixedLinePattern")) {
+ metadata.clearSameMobileAndFixedLinePattern();
+ }
+ if (shouldDrop("mainCountryForCode")) {
+ metadata.clearMainCountryForCode();
+ }
+ if (shouldDrop("mobileNumberPortableRegion")) {
+ metadata.clearMobileNumberPortableRegion();
+ }
+ }
+
+ /**
+ * The input blacklist or whitelist string is expected to be of the form "a(b,c):d(e):f", where
+ * b and c are children of a, e is a child of d, and f is either a parent field, a child field, or
+ * a childless field. Order and whitespace don't matter. We throw RuntimeException for any
+ * duplicates, malformed strings, or strings where field tokens do not correspond to strings in
+ * the sets of excludable fields. We also throw RuntimeException for empty strings since such
+ * strings should be treated as a special case by the flag checking code and not passed here.
+ */
+ // @VisibleForTesting
+ static TreeMap> parseFieldMapFromString(String string) {
+ if (string == null) {
+ throw new RuntimeException("Null string should not be passed to parseFieldMapFromString");
+ }
+ // Remove whitespace.
+ string = string.replaceAll("\\s", "");
+ if (string.isEmpty()) {
+ throw new RuntimeException("Empty string should not be passed to parseFieldMapFromString");
+ }
+
+ TreeMap> fieldMap = new TreeMap>();
+ TreeSet wildcardChildren = new TreeSet();
+ for (String group : string.split(":", -1)) {
+ int leftParenIndex = group.indexOf('(');
+ int rightParenIndex = group.indexOf(')');
+ if (leftParenIndex < 0 && rightParenIndex < 0) {
+ if (excludableParentFields.contains(group)) {
+ if (fieldMap.containsKey(group)) {
+ throw new RuntimeException(group + " given more than once in " + string);
+ }
+ fieldMap.put(group, new TreeSet(excludableChildFields));
+ } else if (excludableChildlessFields.contains(group)) {
+ if (fieldMap.containsKey(group)) {
+ throw new RuntimeException(group + " given more than once in " + string);
+ }
+ fieldMap.put(group, new TreeSet());
+ } else if (excludableChildFields.contains(group)) {
+ if (wildcardChildren.contains(group)) {
+ throw new RuntimeException(group + " given more than once in " + string);
+ }
+ wildcardChildren.add(group);
+ } else {
+ throw new RuntimeException(group + " is not a valid token");
+ }
+ } else if (leftParenIndex > 0 && rightParenIndex == group.length() - 1) {
+ // We don't check for duplicate parentheses or illegal characters since these will be caught
+ // as not being part of valid field tokens.
+ String parent = group.substring(0, leftParenIndex);
+ if (!excludableParentFields.contains(parent)) {
+ throw new RuntimeException(parent + " is not a valid parent token");
+ }
+ if (fieldMap.containsKey(parent)) {
+ throw new RuntimeException(parent + " given more than once in " + string);
+ }
+ TreeSet children = new TreeSet();
+ for (String child : group.substring(leftParenIndex + 1, rightParenIndex).split(",", -1)) {
+ if (!excludableChildFields.contains(child)) {
+ throw new RuntimeException(child + " is not a valid child token");
+ }
+ if (!children.add(child)) {
+ throw new RuntimeException(child + " given more than once in " + group);
+ }
+ }
+ fieldMap.put(parent, children);
+ } else {
+ throw new RuntimeException("Incorrect location of parantheses in " + group);
+ }
+ }
+ for (String wildcardChild : wildcardChildren) {
+ for (String parent : excludableParentFields) {
+ TreeSet children = fieldMap.get(parent);
+ if (children == null) {
+ children = new TreeSet();
+ fieldMap.put(parent, children);
+ }
+ if (!children.add(wildcardChild)
+ && fieldMap.get(parent).size() != excludableChildFields.size()) {
+ // The map already contains parent -> wildcardChild but not all possible children.
+ // So wildcardChild was given explicitly as a child of parent, which is a duplication
+ // since it's also given as a wildcard child.
+ throw new RuntimeException(
+ wildcardChild + " is present by itself so remove it from " + parent + "'s group");
+ }
+ }
+ }
+ return fieldMap;
+ }
+
+ // Does not check that legal tokens are used, assuming that fieldMap is constructed using
+ // parseFieldMapFromString(String) which does check. If fieldMap contains illegal tokens or parent
+ // fields with no children or other unexpected state, the behavior of this function is undefined.
+ // @VisibleForTesting
+ static TreeMap> computeComplement(
+ TreeMap> fieldMap) {
+ TreeMap> complement = new TreeMap>();
+ for (String parent : excludableParentFields) {
+ if (!fieldMap.containsKey(parent)) {
+ complement.put(parent, new TreeSet(excludableChildFields));
+ } else {
+ TreeSet otherChildren = fieldMap.get(parent);
+ // If the other map has all the children for this parent then we don't want to include the
+ // parent as a key.
+ if (otherChildren.size() != excludableChildFields.size()) {
+ TreeSet children = new TreeSet();
+ for (String child : excludableChildFields) {
+ if (!otherChildren.contains(child)) {
+ children.add(child);
+ }
+ }
+ complement.put(parent, children);
+ }
+ }
+ }
+ for (String childlessField : excludableChildlessFields) {
+ if (!fieldMap.containsKey(childlessField)) {
+ complement.put(childlessField, new TreeSet());
+ }
+ }
+ return complement;
+ }
+
+ // @VisibleForTesting
+ boolean shouldDrop(String parent, String child) {
+ if (!excludableParentFields.contains(parent)) {
+ throw new RuntimeException(parent + " is not an excludable parent field");
+ }
+ if (!excludableChildFields.contains(child)) {
+ throw new RuntimeException(child + " is not an excludable child field");
+ }
+ return blacklist.containsKey(parent) && blacklist.get(parent).contains(child);
+ }
+
+ // @VisibleForTesting
+ boolean shouldDrop(String childlessField) {
+ if (!excludableChildlessFields.contains(childlessField)) {
+ throw new RuntimeException(childlessField + " is not an excludable childless field");
+ }
+ return blacklist.containsKey(childlessField);
+ }
+
+ private PhoneNumberDesc getFiltered(String type, PhoneNumberDesc desc) {
+ PhoneNumberDesc.Builder builder = PhoneNumberDesc.newBuilder().mergeFrom(desc);
+ if (shouldDrop(type, "nationalNumberPattern")) {
+ builder.clearNationalNumberPattern();
+ }
+ if (shouldDrop(type, "possibleLength")) {
+ builder.clearPossibleLength();
+ }
+ if (shouldDrop(type, "possibleLengthLocalOnly")) {
+ builder.clearPossibleLengthLocalOnly();
+ }
+ if (shouldDrop(type, "exampleNumber")) {
+ builder.clearExampleNumber();
+ }
+ return builder.build();
+ }
+}
diff --git a/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/AlternateFormatsCountryCodeSet.java b/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/AlternateFormatsCountryCodeSet.java
new file mode 100644
index 0000000000..3975c2d3a0
--- /dev/null
+++ b/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/AlternateFormatsCountryCodeSet.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/* This file is automatically generated by {@link BuildMetadataProtoFromXml}.
+ * Please don't modify it directly.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class AlternateFormatsCountryCodeSet {
+ // A set of all country codes for which data is available.
+ public static Set getCountryCodeSet() {
+ // The capacity is set to 61 as there are 46 different entries,
+ // and this offers a load factor of roughly 0.75.
+ Set countryCodeSet = new HashSet(61);
+
+ countryCodeSet.add(7);
+ countryCodeSet.add(27);
+ countryCodeSet.add(30);
+ countryCodeSet.add(31);
+ countryCodeSet.add(34);
+ countryCodeSet.add(36);
+ countryCodeSet.add(39);
+ countryCodeSet.add(43);
+ countryCodeSet.add(44);
+ countryCodeSet.add(49);
+ countryCodeSet.add(52);
+ countryCodeSet.add(54);
+ countryCodeSet.add(55);
+ countryCodeSet.add(58);
+ countryCodeSet.add(61);
+ countryCodeSet.add(62);
+ countryCodeSet.add(64);
+ countryCodeSet.add(66);
+ countryCodeSet.add(81);
+ countryCodeSet.add(84);
+ countryCodeSet.add(90);
+ countryCodeSet.add(91);
+ countryCodeSet.add(94);
+ countryCodeSet.add(95);
+ countryCodeSet.add(255);
+ countryCodeSet.add(350);
+ countryCodeSet.add(351);
+ countryCodeSet.add(352);
+ countryCodeSet.add(358);
+ countryCodeSet.add(359);
+ countryCodeSet.add(372);
+ countryCodeSet.add(373);
+ countryCodeSet.add(380);
+ countryCodeSet.add(381);
+ countryCodeSet.add(385);
+ countryCodeSet.add(505);
+ countryCodeSet.add(506);
+ countryCodeSet.add(595);
+ countryCodeSet.add(675);
+ countryCodeSet.add(676);
+ countryCodeSet.add(679);
+ countryCodeSet.add(855);
+ countryCodeSet.add(856);
+ countryCodeSet.add(971);
+ countryCodeSet.add(972);
+ countryCodeSet.add(995);
+
+ return countryCodeSet;
+ }
+}
diff --git a/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/AsYouTypeFormatter.java b/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
new file mode 100644
index 0000000000..be11be57ea
--- /dev/null
+++ b/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright (C) 2009 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.internal.RegexCache;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A formatter which formats phone numbers as they are entered.
+ *
+ * An AsYouTypeFormatter can be created by invoking
+ * {@link PhoneNumberUtil#getAsYouTypeFormatter}. After that, digits can be added by invoking
+ * {@link #inputDigit} on the formatter instance, and the partially formatted phone number will be
+ * returned each time a digit is added. {@link #clear} can be invoked before formatting a new
+ * number.
+ *
+ *
See the unittests for more details on how the formatter is to be used.
+ *
+ * @author Shaopeng Jia
+ */
+public class AsYouTypeFormatter {
+ private String currentOutput = "";
+ private StringBuilder formattingTemplate = new StringBuilder();
+ // The pattern from numberFormat that is currently used to create formattingTemplate.
+ private String currentFormattingPattern = "";
+ private StringBuilder accruedInput = new StringBuilder();
+ private StringBuilder accruedInputWithoutFormatting = new StringBuilder();
+ // This indicates whether AsYouTypeFormatter is currently doing the formatting.
+ private boolean ableToFormat = true;
+ // Set to true when users enter their own formatting. AsYouTypeFormatter will do no formatting at
+ // all when this is set to true.
+ private boolean inputHasFormatting = false;
+ // This is set to true when we know the user is entering a full national significant number, since
+ // we have either detected a national prefix or an international dialing prefix. When this is
+ // true, we will no longer use local number formatting patterns.
+ private boolean isCompleteNumber = false;
+ private boolean isExpectingCountryCallingCode = false;
+ private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
+ private String defaultCountry;
+
+ // Character used when appropriate to separate a prefix, such as a long NDD or a country calling
+ // code, from the national number.
+ private static final char SEPARATOR_BEFORE_NATIONAL_NUMBER = ' ';
+ private static final PhoneMetadata EMPTY_METADATA =
+ PhoneMetadata.newBuilder().setId("").setInternationalPrefix("NA").build();
+ private PhoneMetadata defaultMetadata;
+ private PhoneMetadata currentMetadata;
+
+ // A pattern that is used to determine if a numberFormat under availableFormats is eligible to be
+ // used by the AYTF. It is eligible when the format element under numberFormat contains groups of
+ // the dollar sign followed by a single digit, separated by valid phone number punctuation. This
+ // prevents invalid punctuation (such as the star sign in Israeli star numbers) getting into the
+ // output of the AYTF. We require that the first group is present in the output pattern to ensure
+ // no data is lost while formatting; when we format as you type, this should always be the case.
+ private static final Pattern ELIGIBLE_FORMAT_PATTERN =
+ Pattern.compile("[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*"
+ + "\\$1" + "[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*(\\$\\d"
+ + "[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*)*");
+ // A set of characters that, if found in a national prefix formatting rules, are an indicator to
+ // us that we should separate the national prefix from the number when formatting.
+ private static final Pattern NATIONAL_PREFIX_SEPARATORS_PATTERN = Pattern.compile("[- ]");
+
+ // This is the minimum length of national number accrued that is required to trigger the
+ // formatter. The first element of the leadingDigitsPattern of each numberFormat contains a
+ // regular expression that matches up to this number of digits.
+ private static final int MIN_LEADING_DIGITS_LENGTH = 3;
+
+ // The digits that have not been entered yet will be represented by a \u2008, the punctuation
+ // space.
+ private static final String DIGIT_PLACEHOLDER = "\u2008";
+ private static final Pattern DIGIT_PATTERN = Pattern.compile(DIGIT_PLACEHOLDER);
+ private int lastMatchPosition = 0;
+ // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as
+ // found in the original sequence of characters the user entered.
+ private int originalPosition = 0;
+ // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as
+ // found in accruedInputWithoutFormatting.
+ private int positionToRemember = 0;
+ // This contains anything that has been entered so far preceding the national significant number,
+ // and it is formatted (e.g. with space inserted). For example, this can contain IDD, country
+ // code, and/or NDD, etc.
+ private StringBuilder prefixBeforeNationalNumber = new StringBuilder();
+ private boolean shouldAddSpaceAfterNationalPrefix = false;
+ // This contains the national prefix that has been extracted. It contains only digits without
+ // formatting.
+ private String extractedNationalPrefix = "";
+ private StringBuilder nationalNumber = new StringBuilder();
+ private List possibleFormats = new ArrayList();
+
+ // A cache for frequently used country-specific regular expressions.
+ private RegexCache regexCache = new RegexCache(64);
+
+ /**
+ * Constructs an as-you-type formatter. Should be obtained from {@link
+ * PhoneNumberUtil#getAsYouTypeFormatter}.
+ *
+ * @param regionCode the country/region where the phone number is being entered
+ */
+ AsYouTypeFormatter(String regionCode) {
+ defaultCountry = regionCode;
+ currentMetadata = getMetadataForRegion(defaultCountry);
+ defaultMetadata = currentMetadata;
+ }
+
+ // The metadata needed by this class is the same for all regions sharing the same country calling
+ // code. Therefore, we return the metadata for "main" region for this country calling code.
+ private PhoneMetadata getMetadataForRegion(String regionCode) {
+ int countryCallingCode = phoneUtil.getCountryCodeForRegion(regionCode);
+ String mainCountry = phoneUtil.getRegionCodeForCountryCode(countryCallingCode);
+ PhoneMetadata metadata = phoneUtil.getMetadataForRegion(mainCountry);
+ if (metadata != null) {
+ return metadata;
+ }
+ // Set to a default instance of the metadata. This allows us to function with an incorrect
+ // region code, even if formatting only works for numbers specified with "+".
+ return EMPTY_METADATA;
+ }
+
+ // Returns true if a new template is created as opposed to reusing the existing template.
+ private boolean maybeCreateNewTemplate() {
+ // When there are multiple available formats, the formatter uses the first format where a
+ // formatting template could be created.
+ Iterator it = possibleFormats.iterator();
+ while (it.hasNext()) {
+ NumberFormat numberFormat = it.next();
+ String pattern = numberFormat.getPattern();
+ if (currentFormattingPattern.equals(pattern)) {
+ return false;
+ }
+ if (createFormattingTemplate(numberFormat)) {
+ currentFormattingPattern = pattern;
+ shouldAddSpaceAfterNationalPrefix =
+ NATIONAL_PREFIX_SEPARATORS_PATTERN.matcher(
+ numberFormat.getNationalPrefixFormattingRule()).find();
+ // With a new formatting template, the matched position using the old template needs to be
+ // reset.
+ lastMatchPosition = 0;
+ return true;
+ } else { // Remove the current number format from possibleFormats.
+ it.remove();
+ }
+ }
+ ableToFormat = false;
+ return false;
+ }
+
+ private void getAvailableFormats(String leadingDigits) {
+ // First decide whether we should use international or national number rules.
+ boolean isInternationalNumber = isCompleteNumber && extractedNationalPrefix.length() == 0;
+ List formatList =
+ (isInternationalNumber && currentMetadata.getIntlNumberFormatCount() > 0)
+ ? currentMetadata.getIntlNumberFormatList()
+ : currentMetadata.getNumberFormatList();
+ for (NumberFormat format : formatList) {
+ // Discard a few formats that we know are not relevant based on the presence of the national
+ // prefix.
+ if (extractedNationalPrefix.length() > 0
+ && PhoneNumberUtil.formattingRuleHasFirstGroupOnly(
+ format.getNationalPrefixFormattingRule())
+ && !format.getNationalPrefixOptionalWhenFormatting()
+ && !format.hasDomesticCarrierCodeFormattingRule()) {
+ // If it is a national number that had a national prefix, any rules that aren't valid with a
+ // national prefix should be excluded. A rule that has a carrier-code formatting rule is
+ // kept since the national prefix might actually be an extracted carrier code - we don't
+ // distinguish between these when extracting it in the AYTF.
+ continue;
+ } else if (extractedNationalPrefix.length() == 0
+ && !isCompleteNumber
+ && !PhoneNumberUtil.formattingRuleHasFirstGroupOnly(
+ format.getNationalPrefixFormattingRule())
+ && !format.getNationalPrefixOptionalWhenFormatting()) {
+ // This number was entered without a national prefix, and this formatting rule requires one,
+ // so we discard it.
+ continue;
+ }
+ if (ELIGIBLE_FORMAT_PATTERN.matcher(format.getFormat()).matches()) {
+ possibleFormats.add(format);
+ }
+ }
+ narrowDownPossibleFormats(leadingDigits);
+ }
+
+ private void narrowDownPossibleFormats(String leadingDigits) {
+ int indexOfLeadingDigitsPattern = leadingDigits.length() - MIN_LEADING_DIGITS_LENGTH;
+ Iterator it = possibleFormats.iterator();
+ while (it.hasNext()) {
+ NumberFormat format = it.next();
+ if (format.getLeadingDigitsPatternCount() == 0) {
+ // Keep everything that isn't restricted by leading digits.
+ continue;
+ }
+ int lastLeadingDigitsPattern =
+ Math.min(indexOfLeadingDigitsPattern, format.getLeadingDigitsPatternCount() - 1);
+ Pattern leadingDigitsPattern = regexCache.getPatternForRegex(
+ format.getLeadingDigitsPattern(lastLeadingDigitsPattern));
+ Matcher m = leadingDigitsPattern.matcher(leadingDigits);
+ if (!m.lookingAt()) {
+ it.remove();
+ }
+ }
+ }
+
+ private boolean createFormattingTemplate(NumberFormat format) {
+ String numberPattern = format.getPattern();
+ formattingTemplate.setLength(0);
+ String tempTemplate = getFormattingTemplate(numberPattern, format.getFormat());
+ if (tempTemplate.length() > 0) {
+ formattingTemplate.append(tempTemplate);
+ return true;
+ }
+ return false;
+ }
+
+ // Gets a formatting template which can be used to efficiently format a partial number where
+ // digits are added one by one.
+ private String getFormattingTemplate(String numberPattern, String numberFormat) {
+ // Creates a phone number consisting only of the digit 9 that matches the
+ // numberPattern by applying the pattern to the longestPhoneNumber string.
+ String longestPhoneNumber = "999999999999999";
+ Matcher m = regexCache.getPatternForRegex(numberPattern).matcher(longestPhoneNumber);
+ m.find(); // this will always succeed
+ String aPhoneNumber = m.group();
+ // No formatting template can be created if the number of digits entered so far is longer than
+ // the maximum the current formatting rule can accommodate.
+ if (aPhoneNumber.length() < nationalNumber.length()) {
+ return "";
+ }
+ // Formats the number according to numberFormat
+ String template = aPhoneNumber.replaceAll(numberPattern, numberFormat);
+ // Replaces each digit with character DIGIT_PLACEHOLDER
+ template = template.replaceAll("9", DIGIT_PLACEHOLDER);
+ return template;
+ }
+
+ /**
+ * Clears the internal state of the formatter, so it can be reused.
+ */
+ public void clear() {
+ currentOutput = "";
+ accruedInput.setLength(0);
+ accruedInputWithoutFormatting.setLength(0);
+ formattingTemplate.setLength(0);
+ lastMatchPosition = 0;
+ currentFormattingPattern = "";
+ prefixBeforeNationalNumber.setLength(0);
+ extractedNationalPrefix = "";
+ nationalNumber.setLength(0);
+ ableToFormat = true;
+ inputHasFormatting = false;
+ positionToRemember = 0;
+ originalPosition = 0;
+ isCompleteNumber = false;
+ isExpectingCountryCallingCode = false;
+ possibleFormats.clear();
+ shouldAddSpaceAfterNationalPrefix = false;
+ if (!currentMetadata.equals(defaultMetadata)) {
+ currentMetadata = getMetadataForRegion(defaultCountry);
+ }
+ }
+
+ /**
+ * Formats a phone number on-the-fly as each digit is entered.
+ *
+ * @param nextChar the most recently entered digit of a phone number. Formatting characters are
+ * allowed, but as soon as they are encountered this method formats the number as entered and
+ * not "as you type" anymore. Full width digits and Arabic-indic digits are allowed, and will
+ * be shown as they are.
+ * @return the partially formatted phone number.
+ */
+ public String inputDigit(char nextChar) {
+ currentOutput = inputDigitWithOptionToRememberPosition(nextChar, false);
+ return currentOutput;
+ }
+
+ /**
+ * Same as {@link #inputDigit}, but remembers the position where {@code nextChar} is inserted, so
+ * that it can be retrieved later by using {@link #getRememberedPosition}. The remembered
+ * position will be automatically adjusted if additional formatting characters are later
+ * inserted/removed in front of {@code nextChar}.
+ */
+ public String inputDigitAndRememberPosition(char nextChar) {
+ currentOutput = inputDigitWithOptionToRememberPosition(nextChar, true);
+ return currentOutput;
+ }
+
+ @SuppressWarnings("fallthrough")
+ private String inputDigitWithOptionToRememberPosition(char nextChar, boolean rememberPosition) {
+ accruedInput.append(nextChar);
+ if (rememberPosition) {
+ originalPosition = accruedInput.length();
+ }
+ // We do formatting on-the-fly only when each character entered is either a digit, or a plus
+ // sign (accepted at the start of the number only).
+ if (!isDigitOrLeadingPlusSign(nextChar)) {
+ ableToFormat = false;
+ inputHasFormatting = true;
+ } else {
+ nextChar = normalizeAndAccrueDigitsAndPlusSign(nextChar, rememberPosition);
+ }
+ if (!ableToFormat) {
+ // When we are unable to format because of reasons other than that formatting chars have been
+ // entered, it can be due to really long IDDs or NDDs. If that is the case, we might be able
+ // to do formatting again after extracting them.
+ if (inputHasFormatting) {
+ return accruedInput.toString();
+ } else if (attemptToExtractIdd()) {
+ if (attemptToExtractCountryCallingCode()) {
+ return attemptToChoosePatternWithPrefixExtracted();
+ }
+ } else if (ableToExtractLongerNdd()) {
+ // Add an additional space to separate long NDD and national significant number for
+ // readability. We don't set shouldAddSpaceAfterNationalPrefix to true, since we don't want
+ // this to change later when we choose formatting templates.
+ prefixBeforeNationalNumber.append(SEPARATOR_BEFORE_NATIONAL_NUMBER);
+ return attemptToChoosePatternWithPrefixExtracted();
+ }
+ return accruedInput.toString();
+ }
+
+ // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH digits (the plus
+ // sign is counted as a digit as well for this purpose) have been entered.
+ switch (accruedInputWithoutFormatting.length()) {
+ case 0:
+ case 1:
+ case 2:
+ return accruedInput.toString();
+ case 3:
+ if (attemptToExtractIdd()) {
+ isExpectingCountryCallingCode = true;
+ } else { // No IDD or plus sign is found, might be entering in national format.
+ extractedNationalPrefix = removeNationalPrefixFromNationalNumber();
+ return attemptToChooseFormattingPattern();
+ }
+ // fall through
+ default:
+ if (isExpectingCountryCallingCode) {
+ if (attemptToExtractCountryCallingCode()) {
+ isExpectingCountryCallingCode = false;
+ }
+ return prefixBeforeNationalNumber + nationalNumber.toString();
+ }
+ if (possibleFormats.size() > 0) { // The formatting patterns are already chosen.
+ String tempNationalNumber = inputDigitHelper(nextChar);
+ // See if the accrued digits can be formatted properly already. If not, use the results
+ // from inputDigitHelper, which does formatting based on the formatting pattern chosen.
+ String formattedNumber = attemptToFormatAccruedDigits();
+ if (formattedNumber.length() > 0) {
+ return formattedNumber;
+ }
+ narrowDownPossibleFormats(nationalNumber.toString());
+ if (maybeCreateNewTemplate()) {
+ return inputAccruedNationalNumber();
+ }
+ return ableToFormat
+ ? appendNationalNumber(tempNationalNumber)
+ : accruedInput.toString();
+ } else {
+ return attemptToChooseFormattingPattern();
+ }
+ }
+ }
+
+ private String attemptToChoosePatternWithPrefixExtracted() {
+ ableToFormat = true;
+ isExpectingCountryCallingCode = false;
+ possibleFormats.clear();
+ lastMatchPosition = 0;
+ formattingTemplate.setLength(0);
+ currentFormattingPattern = "";
+ return attemptToChooseFormattingPattern();
+ }
+
+ // @VisibleForTesting
+ String getExtractedNationalPrefix() {
+ return extractedNationalPrefix;
+ }
+
+ // Some national prefixes are a substring of others. If extracting the shorter NDD doesn't result
+ // in a number we can format, we try to see if we can extract a longer version here.
+ private boolean ableToExtractLongerNdd() {
+ if (extractedNationalPrefix.length() > 0) {
+ // Put the extracted NDD back to the national number before attempting to extract a new NDD.
+ nationalNumber.insert(0, extractedNationalPrefix);
+ // Remove the previously extracted NDD from prefixBeforeNationalNumber. We cannot simply set
+ // it to empty string because people sometimes incorrectly enter national prefix after the
+ // country code, e.g. +44 (0)20-1234-5678.
+ int indexOfPreviousNdd = prefixBeforeNationalNumber.lastIndexOf(extractedNationalPrefix);
+ prefixBeforeNationalNumber.setLength(indexOfPreviousNdd);
+ }
+ return !extractedNationalPrefix.equals(removeNationalPrefixFromNationalNumber());
+ }
+
+ private boolean isDigitOrLeadingPlusSign(char nextChar) {
+ return Character.isDigit(nextChar)
+ || (accruedInput.length() == 1
+ && PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(Character.toString(nextChar)).matches());
+ }
+
+ /**
+ * Checks to see if there is an exact pattern match for these digits. If so, we should use this
+ * instead of any other formatting template whose leadingDigitsPattern also matches the input.
+ */
+ String attemptToFormatAccruedDigits() {
+ for (NumberFormat numberFormat : possibleFormats) {
+ Matcher m = regexCache.getPatternForRegex(numberFormat.getPattern()).matcher(nationalNumber);
+ if (m.matches()) {
+ shouldAddSpaceAfterNationalPrefix =
+ NATIONAL_PREFIX_SEPARATORS_PATTERN.matcher(
+ numberFormat.getNationalPrefixFormattingRule()).find();
+ String formattedNumber = m.replaceAll(numberFormat.getFormat());
+ // Check that we did not remove nor add any extra digits when we matched
+ // this formatting pattern. This usually happens after we entered the last
+ // digit during AYTF. Eg: In case of MX, we swallow mobile token (1) when
+ // formatted but AYTF should retain all the number entered and not change
+ // in order to match a format (of same leading digits and length) display
+ // in that way.
+ String fullOutput = appendNationalNumber(formattedNumber);
+ String formattedNumberDigitsOnly = PhoneNumberUtil.normalizeDiallableCharsOnly(fullOutput);
+ if (formattedNumberDigitsOnly.contentEquals(accruedInputWithoutFormatting)) {
+ // If it's the same (i.e entered number and format is same), then it's
+ // safe to return this in formatted number as nothing is lost / added.
+ return fullOutput;
+ }
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Returns the current position in the partially formatted phone number of the character which was
+ * previously passed in as the parameter of {@link #inputDigitAndRememberPosition}.
+ */
+ public int getRememberedPosition() {
+ if (!ableToFormat) {
+ return originalPosition;
+ }
+ int accruedInputIndex = 0;
+ int currentOutputIndex = 0;
+ while (accruedInputIndex < positionToRemember && currentOutputIndex < currentOutput.length()) {
+ if (accruedInputWithoutFormatting.charAt(accruedInputIndex)
+ == currentOutput.charAt(currentOutputIndex)) {
+ accruedInputIndex++;
+ }
+ currentOutputIndex++;
+ }
+ return currentOutputIndex;
+ }
+
+ /**
+ * Combines the national number with any prefix (IDD/+ and country code or national prefix) that
+ * was collected. A space will be inserted between them if the current formatting template
+ * indicates this to be suitable.
+ */
+ private String appendNationalNumber(String nationalNumber) {
+ int prefixBeforeNationalNumberLength = prefixBeforeNationalNumber.length();
+ if (shouldAddSpaceAfterNationalPrefix && prefixBeforeNationalNumberLength > 0
+ && prefixBeforeNationalNumber.charAt(prefixBeforeNationalNumberLength - 1)
+ != SEPARATOR_BEFORE_NATIONAL_NUMBER) {
+ // We want to add a space after the national prefix if the national prefix formatting rule
+ // indicates that this would normally be done, with the exception of the case where we already
+ // appended a space because the NDD was surprisingly long.
+ return new String(prefixBeforeNationalNumber) + SEPARATOR_BEFORE_NATIONAL_NUMBER
+ + nationalNumber;
+ } else {
+ return prefixBeforeNationalNumber + nationalNumber;
+ }
+ }
+
+ /**
+ * Attempts to set the formatting template and returns a string which contains the formatted
+ * version of the digits entered so far.
+ */
+ private String attemptToChooseFormattingPattern() {
+ // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH digits of national
+ // number (excluding national prefix) have been entered.
+ if (nationalNumber.length() >= MIN_LEADING_DIGITS_LENGTH) {
+
+ getAvailableFormats(nationalNumber.toString());
+ // See if the accrued digits can be formatted properly already.
+ String formattedNumber = attemptToFormatAccruedDigits();
+ if (formattedNumber.length() > 0) {
+ return formattedNumber;
+ }
+ return maybeCreateNewTemplate() ? inputAccruedNationalNumber() : accruedInput.toString();
+ } else {
+ return appendNationalNumber(nationalNumber.toString());
+ }
+ }
+
+ /**
+ * Invokes inputDigitHelper on each digit of the national number accrued, and returns a formatted
+ * string in the end.
+ */
+ private String inputAccruedNationalNumber() {
+ int lengthOfNationalNumber = nationalNumber.length();
+ if (lengthOfNationalNumber > 0) {
+ String tempNationalNumber = "";
+ for (int i = 0; i < lengthOfNationalNumber; i++) {
+ tempNationalNumber = inputDigitHelper(nationalNumber.charAt(i));
+ }
+ return ableToFormat ? appendNationalNumber(tempNationalNumber) : accruedInput.toString();
+ } else {
+ return prefixBeforeNationalNumber.toString();
+ }
+ }
+
+ /**
+ * Returns true if the current country is a NANPA country and the national number begins with
+ * the national prefix.
+ */
+ private boolean isNanpaNumberWithNationalPrefix() {
+ // For NANPA numbers beginning with 1[2-9], treat the 1 as the national prefix. The reason is
+ // that national significant numbers in NANPA always start with [2-9] after the national prefix.
+ // Numbers beginning with 1[01] can only be short/emergency numbers, which don't need the
+ // national prefix.
+ return (currentMetadata.getCountryCode() == 1) && (nationalNumber.charAt(0) == '1')
+ && (nationalNumber.charAt(1) != '0') && (nationalNumber.charAt(1) != '1');
+ }
+
+ // Returns the national prefix extracted, or an empty string if it is not present.
+ private String removeNationalPrefixFromNationalNumber() {
+ int startOfNationalNumber = 0;
+ if (isNanpaNumberWithNationalPrefix()) {
+ startOfNationalNumber = 1;
+ prefixBeforeNationalNumber.append('1').append(SEPARATOR_BEFORE_NATIONAL_NUMBER);
+ isCompleteNumber = true;
+ } else if (currentMetadata.hasNationalPrefixForParsing()) {
+ Pattern nationalPrefixForParsing =
+ regexCache.getPatternForRegex(currentMetadata.getNationalPrefixForParsing());
+ Matcher m = nationalPrefixForParsing.matcher(nationalNumber);
+ // Since some national prefix patterns are entirely optional, check that a national prefix
+ // could actually be extracted.
+ if (m.lookingAt() && m.end() > 0) {
+ // When the national prefix is detected, we use international formatting rules instead of
+ // national ones, because national formatting rules could contain local formatting rules
+ // for numbers entered without area code.
+ isCompleteNumber = true;
+ startOfNationalNumber = m.end();
+ prefixBeforeNationalNumber.append(nationalNumber.substring(0, startOfNationalNumber));
+ }
+ }
+ String nationalPrefix = nationalNumber.substring(0, startOfNationalNumber);
+ nationalNumber.delete(0, startOfNationalNumber);
+ return nationalPrefix;
+ }
+
+ /**
+ * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are available, and places
+ * the remaining input into nationalNumber.
+ *
+ * @return true when accruedInputWithoutFormatting begins with the plus sign or valid IDD for
+ * defaultCountry.
+ */
+ private boolean attemptToExtractIdd() {
+ Pattern internationalPrefix =
+ regexCache.getPatternForRegex("\\" + PhoneNumberUtil.PLUS_SIGN + "|"
+ + currentMetadata.getInternationalPrefix());
+ Matcher iddMatcher = internationalPrefix.matcher(accruedInputWithoutFormatting);
+ if (iddMatcher.lookingAt()) {
+ isCompleteNumber = true;
+ int startOfCountryCallingCode = iddMatcher.end();
+ nationalNumber.setLength(0);
+ nationalNumber.append(accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
+ prefixBeforeNationalNumber.setLength(0);
+ prefixBeforeNationalNumber.append(
+ accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
+ if (accruedInputWithoutFormatting.charAt(0) != PhoneNumberUtil.PLUS_SIGN) {
+ prefixBeforeNationalNumber.append(SEPARATOR_BEFORE_NATIONAL_NUMBER);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Extracts the country calling code from the beginning of nationalNumber to
+ * prefixBeforeNationalNumber when they are available, and places the remaining input into
+ * nationalNumber.
+ *
+ * @return true when a valid country calling code can be found.
+ */
+ private boolean attemptToExtractCountryCallingCode() {
+ if (nationalNumber.length() == 0) {
+ return false;
+ }
+ StringBuilder numberWithoutCountryCallingCode = new StringBuilder();
+ int countryCode = phoneUtil.extractCountryCode(nationalNumber, numberWithoutCountryCallingCode);
+ if (countryCode == 0) {
+ return false;
+ }
+ nationalNumber.setLength(0);
+ nationalNumber.append(numberWithoutCountryCallingCode);
+ String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode);
+ if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(newRegionCode)) {
+ currentMetadata = phoneUtil.getMetadataForNonGeographicalRegion(countryCode);
+ } else if (!newRegionCode.equals(defaultCountry)) {
+ currentMetadata = getMetadataForRegion(newRegionCode);
+ }
+ String countryCodeString = Integer.toString(countryCode);
+ prefixBeforeNationalNumber.append(countryCodeString).append(SEPARATOR_BEFORE_NATIONAL_NUMBER);
+ // When we have successfully extracted the IDD, the previously extracted NDD should be cleared
+ // because it is no longer valid.
+ extractedNationalPrefix = "";
+ return true;
+ }
+
+ // Accrues digits and the plus sign to accruedInputWithoutFormatting for later use. If nextChar
+ // contains a digit in non-ASCII format (e.g. the full-width version of digits), it is first
+ // normalized to the ASCII version. The return value is nextChar itself, or its normalized
+ // version, if nextChar is a digit in non-ASCII format. This method assumes its input is either a
+ // digit or the plus sign.
+ private char normalizeAndAccrueDigitsAndPlusSign(char nextChar, boolean rememberPosition) {
+ char normalizedChar;
+ if (nextChar == PhoneNumberUtil.PLUS_SIGN) {
+ normalizedChar = nextChar;
+ accruedInputWithoutFormatting.append(nextChar);
+ } else {
+ int radix = 10;
+ normalizedChar = Character.forDigit(Character.digit(nextChar, radix), radix);
+ accruedInputWithoutFormatting.append(normalizedChar);
+ nationalNumber.append(normalizedChar);
+ }
+ if (rememberPosition) {
+ positionToRemember = accruedInputWithoutFormatting.length();
+ }
+ return normalizedChar;
+ }
+
+ private String inputDigitHelper(char nextChar) {
+ // Note that formattingTemplate is not guaranteed to have a value, it could be empty, e.g.
+ // when the next digit is entered after extracting an IDD or NDD.
+ Matcher digitMatcher = DIGIT_PATTERN.matcher(formattingTemplate);
+ if (digitMatcher.find(lastMatchPosition)) {
+ String tempTemplate = digitMatcher.replaceFirst(Character.toString(nextChar));
+ formattingTemplate.replace(0, tempTemplate.length(), tempTemplate);
+ lastMatchPosition = digitMatcher.start();
+ return formattingTemplate.substring(0, lastMatchPosition + 1);
+ } else {
+ if (possibleFormats.size() == 1) {
+ // More digits are entered than we could handle, and there are no other valid patterns to
+ // try.
+ ableToFormat = false;
+ } // else, we just reset the formatting pattern.
+ currentFormattingPattern = "";
+ return accruedInput.toString();
+ }
+ }
+}
diff --git a/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java b/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
new file mode 100644
index 0000000000..06571d15f0
--- /dev/null
+++ b/tools/java/java-build/target/classes/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
@@ -0,0 +1,942 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+/* This file is automatically generated by {@link BuildMetadataProtoFromXml}.
+ * Please don't modify it directly.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CountryCodeToRegionCodeMap {
+ // A mapping from a country code to the region codes which denote the
+ // country/region represented by that country code. In the case of multiple
+ // countries sharing a calling code, such as the NANPA countries, the one
+ // indicated with "isMainCountryForCode" in the metadata should be first.
+ public static Map> getCountryCodeToRegionCodeMap() {
+ // The capacity is set to 286 as there are 215 different entries,
+ // and this offers a load factor of roughly 0.75.
+ Map> countryCodeToRegionCodeMap =
+ new HashMap>(286);
+
+ ArrayList listWithRegionCode;
+
+ listWithRegionCode = new ArrayList(25);
+ listWithRegionCode.add("US");
+ listWithRegionCode.add("AG");
+ listWithRegionCode.add("AI");
+ listWithRegionCode.add("AS");
+ listWithRegionCode.add("BB");
+ listWithRegionCode.add("BM");
+ listWithRegionCode.add("BS");
+ listWithRegionCode.add("CA");
+ listWithRegionCode.add("DM");
+ listWithRegionCode.add("DO");
+ listWithRegionCode.add("GD");
+ listWithRegionCode.add("GU");
+ listWithRegionCode.add("JM");
+ listWithRegionCode.add("KN");
+ listWithRegionCode.add("KY");
+ listWithRegionCode.add("LC");
+ listWithRegionCode.add("MP");
+ listWithRegionCode.add("MS");
+ listWithRegionCode.add("PR");
+ listWithRegionCode.add("SX");
+ listWithRegionCode.add("TC");
+ listWithRegionCode.add("TT");
+ listWithRegionCode.add("VC");
+ listWithRegionCode.add("VG");
+ listWithRegionCode.add("VI");
+ countryCodeToRegionCodeMap.put(1, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(2);
+ listWithRegionCode.add("RU");
+ listWithRegionCode.add("KZ");
+ countryCodeToRegionCodeMap.put(7, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("EG");
+ countryCodeToRegionCodeMap.put(20, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("ZA");
+ countryCodeToRegionCodeMap.put(27, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("GR");
+ countryCodeToRegionCodeMap.put(30, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("NL");
+ countryCodeToRegionCodeMap.put(31, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("BE");
+ countryCodeToRegionCodeMap.put(32, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("FR");
+ countryCodeToRegionCodeMap.put(33, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("ES");
+ countryCodeToRegionCodeMap.put(34, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("HU");
+ countryCodeToRegionCodeMap.put(36, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(2);
+ listWithRegionCode.add("IT");
+ listWithRegionCode.add("VA");
+ countryCodeToRegionCodeMap.put(39, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("RO");
+ countryCodeToRegionCodeMap.put(40, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("CH");
+ countryCodeToRegionCodeMap.put(41, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("AT");
+ countryCodeToRegionCodeMap.put(43, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(4);
+ listWithRegionCode.add("GB");
+ listWithRegionCode.add("GG");
+ listWithRegionCode.add("IM");
+ listWithRegionCode.add("JE");
+ countryCodeToRegionCodeMap.put(44, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("DK");
+ countryCodeToRegionCodeMap.put(45, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList(1);
+ listWithRegionCode.add("SE");
+ countryCodeToRegionCodeMap.put(46, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList