10d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com/*
20d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * Copyright (C) 2010 Google Inc.
30d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com *
40d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * Licensed under the Apache License, Version 2.0 (the "License");
50d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * you may not use this file except in compliance with the License.
60d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * You may obtain a copy of the License at
70d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com *
80d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * http://www.apache.org/licenses/LICENSE-2.0
90d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com *
100d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * Unless required by applicable law or agreed to in writing, software
110d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * distributed under the License is distributed on an "AS IS" BASIS,
120d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * See the License for the specific language governing permissions and
140d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com * limitations under the License.
150d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com */
160d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com
170d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.compackage com.android.i18n.addressinput;
180d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com
19c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.comimport com.android.i18n.addressinput.LookupKey.ScriptType;
20c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com
219be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.comimport java.util.EnumSet;
220d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.comimport java.util.HashSet;
230d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.comimport java.util.Map;
240d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.comimport java.util.Set;
250d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.comimport java.util.regex.Pattern;
260d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com
270d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com/**
28c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com * Accesses address verification data used to verify components of an address.
29c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com * <p> Not all fields require all types of validation, although this could be done. In particular,
30c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com * the current implementation only provides known value verification for the hierarchical fields,
31c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com * and only provides format and match verification for the postal code field.
320d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com */
330d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.compublic class FieldVerifier {
34c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // Node data values are delimited by this symbol.
350dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com    private static final String DATA_DELIMITER = "~";
36c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // Keys are built up using this delimiter: eg data/US, data/US/CA.
370dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com    private static final String KEY_DELIMITER = "/";
38c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com
3937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private String mId;
4037ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private DataSource mDataSource;
412c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
4237ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private Set<AddressField> mPossibleFields;
4337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private Set<AddressField> mRequired;
442c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    // Known values. Can be either a key, a name in Latin, or a name in native script.
4537ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private Map<String, String> mCandidateValues;
462c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
47c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // Keys for the subnodes of this verifier. For example, a key for the US would be CA, since
48c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // there is a sub-verifier with the ID "data/US/CA". Keys may be the local names of the
49c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // locations in the next level of the hierarchy, or the abbreviations if suitable abbreviations
50c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // exist.
5137ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private String[] mKeys;
52c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // Names in Latin. These are only populated if the native/local names are in a script other than
53c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // latin.
5437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private String[] mLatinNames;
552c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    // Names in native script.
5637ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private String[] mLocalNames;
572c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
58c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // Pattern representing the format of a postal code number.
5937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private Pattern mFormat;
60c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    // Defines the valid range of a postal code number.
6137ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private Pattern mMatch;
622c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
63e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    /**
64e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com     * Creates the root field verifier for a particular data source.
65e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com     */
66e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    public FieldVerifier(DataSource dataSource) {
67ff01be390a0c4cbc5eb51ea8e2aceabfd1cbe2a3lararennie@google.com        mDataSource = dataSource;
68e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        populateRootVerifier();
699be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com    }
702c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
71e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    /**
72e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com     * Creates a field verifier based on its parent and on the new data for this node supplied by
73e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com     * nodeData (which may be null).
74e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com     */
75e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    private FieldVerifier(FieldVerifier parent, AddressVerificationNodeData nodeData) {
76e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        // Most information is inherited from the parent.
7737ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mPossibleFields = parent.mPossibleFields;
7837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mRequired = parent.mRequired;
7937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mDataSource = parent.mDataSource;
8037ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mFormat = parent.mFormat;
8137ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mMatch = parent.mMatch;
82e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        // Here we add in any overrides from this particular node as well as information such as
83c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // localNames, latinNames and keys.
842c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        populate(nodeData);
85c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // candidateValues should never be inherited from the parent, but built up from the
86c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // localNames in this node.
8737ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mCandidateValues = Util.buildNameToKeyMap(mKeys, mLocalNames, mLatinNames);
88e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    }
89e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com
90e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    /**
91120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com     * Sets possibleFieldsUsed, required, keys and candidateValues for the root field verifier. This
92120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com     * is a little messy at the moment since not all the appropriate information is actually under
93120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com     * the root "data" node in the metadata. For example, "possibleFields" and "required" are not
94120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com     * present there.
95e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com     */
96e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com    private void populateRootVerifier() {
9737ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mId = "data";
98c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // Keys come from the countries under "data".
9937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        AddressVerificationNodeData rootNode = mDataSource.getDefaultData("data");
100c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        if (rootNode.containsKey(AddressDataKey.COUNTRIES)) {
1010dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com            mKeys = rootNode.get(AddressDataKey.COUNTRIES).split(DATA_DELIMITER);
102c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        }
103c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // candidateValues is just the set of keys.
10437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mCandidateValues = Util.buildNameToKeyMap(mKeys, null, null);
105e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com
1067f76d87bdccd605e2ee1d3f4a9e2943d6a4446f0lararennie@google.com        // Copy "possibleFieldsUsed" and "required" from the defaults here for bootstrapping.
107c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // TODO: Investigate a cleaner way of doing this - maybe we should populate "data" with this
108c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // information instead.
10937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        AddressVerificationNodeData defaultZZ = mDataSource.getDefaultData("data/ZZ");
11037ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mPossibleFields = new HashSet<AddressField>();
111e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        if (defaultZZ.containsKey(AddressDataKey.FMT)) {
11237ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mPossibleFields = parseAddressFields(defaultZZ.get(AddressDataKey.FMT));
113e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        }
11437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mRequired = new HashSet<AddressField>();
115e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        if (defaultZZ.containsKey(AddressDataKey.REQUIRE)) {
11637ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mRequired = parseRequireString(defaultZZ.get(AddressDataKey.REQUIRE));
1172c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
1189be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com    }
1192c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
120c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
121c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Populates this verifier with data from the node data passed in. This may be null.
122c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
1232c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private void populate(AddressVerificationNodeData nodeData) {
124e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        if (nodeData == null) {
125e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com            return;
126e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        }
1272c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.ID)) {
12837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mId = nodeData.get(AddressDataKey.ID);
1299be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
1302c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.SUB_KEYS)) {
1310dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com            mKeys = nodeData.get(AddressDataKey.SUB_KEYS).split(DATA_DELIMITER);
1329be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
1332c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.SUB_LNAMES)) {
1340dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com            mLatinNames = nodeData.get(AddressDataKey.SUB_LNAMES).split(DATA_DELIMITER);
1359be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
1362c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.SUB_NAMES)) {
1370dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com            mLocalNames = nodeData.get(AddressDataKey.SUB_NAMES).split(DATA_DELIMITER);
1380d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com        }
1392c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.FMT)) {
14037ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mPossibleFields = parseAddressFields(nodeData.get(AddressDataKey.FMT));
1419be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
1422c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.REQUIRE)) {
14337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mRequired = parseRequireString(nodeData.get(AddressDataKey.REQUIRE));
1442c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
145c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        if (nodeData.containsKey(AddressDataKey.XZIP)) {
14679da571b267bea91531dcc2429ae8c15269e9545lararennie@google.com            mFormat = Pattern.compile(nodeData.get(AddressDataKey.XZIP), Pattern.CASE_INSENSITIVE);
147c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        }
1482c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (nodeData.containsKey(AddressDataKey.ZIP)) {
149c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com            // This key has two different meanings, depending on whether this is a country-level key
150c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com            // or not.
1512c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            if (isCountryKey()) {
15279da571b267bea91531dcc2429ae8c15269e9545lararennie@google.com                mFormat = Pattern.compile(nodeData.get(AddressDataKey.ZIP),
15379da571b267bea91531dcc2429ae8c15269e9545lararennie@google.com                                          Pattern.CASE_INSENSITIVE);
1542c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            } else {
15579da571b267bea91531dcc2429ae8c15269e9545lararennie@google.com                mMatch = Pattern.compile(nodeData.get(AddressDataKey.ZIP),
15679da571b267bea91531dcc2429ae8c15269e9545lararennie@google.com                                         Pattern.CASE_INSENSITIVE);
1572c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
1582c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
159c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // If there are latin names but no local names, and there are the same number of latin names
160c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // as there are keys, then we assume the local names are the same as the keys.
16137ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        if (mKeys != null && mLocalNames == null && mLatinNames != null &&
16237ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mKeys.length == mLatinNames.length) {
16337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mLocalNames = mKeys;
1649be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
1650d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com    }
1662c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1672c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    FieldVerifier refineVerifier(String sublevel) {
168e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        if (Util.trimToNull(sublevel) == null) {
169e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com            return new FieldVerifier(this, null);
170e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        }
171f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com        // If the parent node didn't exist, then the subLevelName will start with "null".
1720dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com        String subLevelName = mId + KEY_DELIMITER + sublevel;
173f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com        // For names with no Latin equivalent, we can look up the sublevel name directly.
17437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        AddressVerificationNodeData nodeData = mDataSource.get(subLevelName);
175e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        if (nodeData != null) {
176e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com            return new FieldVerifier(this, nodeData);
177e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        }
178e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        // If that failed, then we try to look up the local name equivalent of this latin name.
179e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        // First check these exist.
18037ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        if (mLatinNames == null) {
181e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com            return new FieldVerifier(this, null);
182e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        }
18337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        for (int n = 0; n < mLatinNames.length; n++) {
18437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            if (mLatinNames[n].equalsIgnoreCase(sublevel)) {
185e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com                // We found a match - we should try looking up a key with the local name at the same
186e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com                // index.
1870dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com                subLevelName = mId + KEY_DELIMITER + mLocalNames[n];
18837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                nodeData = mDataSource.get(subLevelName);
189e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com                if (nodeData != null) {
1902c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    return new FieldVerifier(this, nodeData);
1912c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
1922c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
1932c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
194e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        // No sub-verifiers were found.
195e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        return new FieldVerifier(this, null);
1960d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com    }
1972c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
198c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
199c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Returns the ID of this verifier.
200c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
2011e17a681ee8e5566384ead1588f6e6c4c8aa333aroubert@google.com    @Override
202c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    public String toString() {
20337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        return mId;
204c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    }
205c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com
206c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
207c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Checks a value in a particular script for a particular field to see if it causes the problem
208c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * specified. If so, this problem is added to the AddressProblems collection passed in. Returns
209c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * true if no problem was found.
210c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
211c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    protected boolean check(ScriptType script, AddressProblemType problem, AddressField field,
212c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com            String value, AddressProblems problems) {
2132c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        boolean problemFound = false;
2142c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2152c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        String trimmedValue = Util.trimToNull(value);
2162c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        switch (problem) {
2172d77bd892a00b3761360aff3c421cc76949b2630lararennie@google.com            case USING_UNUSED_FIELD:
21837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                if (trimmedValue != null && !mPossibleFields.contains(field)) {
2192c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    problemFound = true;
2202c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2212c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                break;
2222c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            case MISSING_REQUIRED_FIELD:
22337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                if (mRequired.contains(field) && trimmedValue == null) {
2242c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    problemFound = true;
2252c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2262c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                break;
2272c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            case UNKNOWN_VALUE:
2282c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // An empty string will never be an UNKNOWN_VALUE. It is invalid
2292c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // only when it appears in a required field (In that case it will
2302c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // be reported as MISSING_REQUIRED_FIELD).
2312c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                if (trimmedValue == null) {
2322c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    break;
2332c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
234e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com                problemFound = !isKnownInScript(script, trimmedValue);
2352c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                break;
2362c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            case UNRECOGNIZED_FORMAT:
23737ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                if (trimmedValue != null && mFormat != null &&
23837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                        !mFormat.matcher(trimmedValue).matches()) {
2392c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    problemFound = true;
2402c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2412c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                break;
2422c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            case MISMATCHING_VALUE:
24337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                if (trimmedValue != null && mMatch != null &&
24437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                        !mMatch.matcher(trimmedValue).lookingAt()) {
2452c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    problemFound = true;
2462c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2472c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                break;
2482c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            default:
249c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com                throw new RuntimeException("Unknown problem: " + problem);
2502c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
2512c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (problemFound) {
2522c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            problems.add(field, problem);
2532c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
2542c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return !problemFound;
2550d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com    }
2560d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com
257c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
258c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Checks the value of a particular field in a particular script against the known values for
259c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * this field. If script is null, it checks both the local and the latin values. Otherwise it
260c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * checks only the values in the script specified.
261c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
262c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    private boolean isKnownInScript(ScriptType script, String value) {
2632c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        String trimmedValue = Util.trimToNull(value);
2642c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        Util.checkNotNull(trimmedValue);
2652c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (script == null) {
26637ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            return (mCandidateValues == null ||
26737ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                    mCandidateValues.containsKey(trimmedValue.toLowerCase()));
2682c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
269c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // Otherwise, if we know the script, we want to restrict the candidates to only names in
270c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // that script.
27137ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        String[] namesToConsider = (script == ScriptType.LATIN) ? mLatinNames : mLocalNames;
2722c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        Set<String> candidates = new HashSet<String>();
273c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        if (namesToConsider != null) {
274c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com            for (String name : namesToConsider) {
2752c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                candidates.add(name.toLowerCase());
2762c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
2779be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
27837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        if (mKeys != null) {
27937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            for (String name : mKeys) {
2802c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                candidates.add(name.toLowerCase());
2812c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
2829be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com        }
2832c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2842c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (candidates.size() == 0 || trimmedValue == null) {
2852c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            return true;
2862c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
2872c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2882c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return candidates.contains(value.toLowerCase());
2899be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com    }
2902c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
291c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
292c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Parses the value of the "fmt" key in the data to see which fields are used for a particular
293c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * country. Returns a list of all fields found. Country is always assumed to be present. Skips
294c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * characters that indicate new-lines in the format information, as well as any characters not
295c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * escaped with "%".
296c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
2972c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private static Set<AddressField> parseAddressFields(String value) {
2982c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        EnumSet<AddressField> result = EnumSet.of(AddressField.COUNTRY);
2992c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        boolean escaped = false;
3002c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        for (char c : value.toCharArray()) {
3012c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            if (escaped) {
3022c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                escaped = false;
3032c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                if (c == 'n') {
3042c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    continue;
3052c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
3062c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                AddressField f = AddressField.of(c);
3072c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                if (f == null) {
3082c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    throw new RuntimeException(
309c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com                            "Unrecognized character '" + c + "' in format pattern: " + value);
3102c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
3112c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                result.add(f);
3122c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            } else if (c == '%') {
3132c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                escaped = true;
3142c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
3152c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
316c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // These fields are not mentioned in the metadata at the moment since there is an effort to
317c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // move away from STREET_ADDRESS and use these fields instead. This means they have to be
318c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // removed here.
3192c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        result.remove(AddressField.ADDRESS_LINE_1);
3202c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        result.remove(AddressField.ADDRESS_LINE_2);
3212c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
3222c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return result;
3232c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    }
3249be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com
325c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
326c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Parses the value of the "required" key in the data. Adds country as well as any other field
327c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * mentioned in the string.
328c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
3292c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private static Set<AddressField> parseRequireString(String value) {
330e804e26aadcdaa5bff50de31586db0847050a4fflararennie@google.com        // Country is always required
3312c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        EnumSet<AddressField> result = EnumSet.of(AddressField.COUNTRY);
3329be65349790706e3e5d5914587ce8c8e0ac21fbfjeanine@google.com
3332c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        for (char c : value.toCharArray()) {
3342c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            AddressField f = AddressField.of(c);
3352c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            if (f == null) {
3362c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                throw new RuntimeException("Unrecognized character '" + c + "' in require pattern: "
3372c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                        + value);
3382c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
3392c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            result.add(f);
3402c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
341c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // These fields are not mentioned in the metadata at the moment since there is an effort to
342c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // move away from STREET_ADDRESS and use these fields instead. This means they have to be
343c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com        // removed here.
3442c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        result.remove(AddressField.ADDRESS_LINE_1);
3452c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        result.remove(AddressField.ADDRESS_LINE_2);
3462c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
3472c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return result;
3482c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    }
3492c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
350c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com    /**
351c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * Returns true if this key represents a country. We assume all keys with only one delimiter are
352c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     * at the country level (such as "data/US").
353c23d7134d3b7f415c5699e41085676db938bd6e6lararennie@google.com     */
3542c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private boolean isCountryKey() {
35537ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        Util.checkNotNull(mId, "Cannot use null as key");
3560dbcfb4fae3e125550c534191627574ea69ec3aflararennie@google.com        return mId.split(KEY_DELIMITER).length == 2;
3572c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    }
3580d882db3bd1d570723e00d5c0d793544ae0ea258lararennie@google.com}
359