16c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com/*
26c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * Copyright (C) 2010 Google Inc.
36c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com *
46c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * Licensed under the Apache License, Version 2.0 (the "License");
56c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * you may not use this file except in compliance with the License.
66c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * You may obtain a copy of the License at
76c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com *
86c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * http://www.apache.org/licenses/LICENSE-2.0
96c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com *
106c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * Unless required by applicable law or agreed to in writing, software
116c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * distributed under the License is distributed on an "AS IS" BASIS,
126c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * See the License for the specific language governing permissions and
146c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * limitations under the License.
156c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com */
166c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
176c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.compackage com.android.i18n.addressinput;
186c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
196c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.comimport com.android.i18n.addressinput.LookupKey.KeyType;
206c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
21d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.comimport android.util.Log;
22d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com
236c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.comimport org.json.JSONArray;
246c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.comimport org.json.JSONException;
256c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
266c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.comimport java.util.EnumMap;
276c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.comimport java.util.HashMap;
28579db66046292a306a96dc478fa17da605fdda6croubert@google.comimport java.util.HashSet;
296c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.comimport java.util.Map;
30579db66046292a306a96dc478fa17da605fdda6croubert@google.comimport java.util.Set;
316c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
326c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com/**
336c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * Access point for the cached address verification data. The data contained here will mainly be
346c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com * used to build {@link FieldVerifier}'s. This class is implemented as a singleton.
356c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com */
364c22ccb59af2b2feca7188afe5a0e3ec04a3c913jeanine@google.compublic class ClientData implements DataSource {
376c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
38d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com    private static final String TAG = "ClientData";
39d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com
40d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com    /**
41d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com     * Data to bootstrap the process. The data are all regional (country level)
422c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * data. Keys are like "data/US/CA"
432c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     */
4437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private final Map<String, JsoMap> mBootstrapMap = new HashMap<String, JsoMap>();
456c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
4637ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com    private CacheData mCacheData;
472c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
484c22ccb59af2b2feca7188afe5a0e3ec04a3c913jeanine@google.com    public ClientData(CacheData cacheData) {
4937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        this.mCacheData = cacheData;
502c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        buildRegionalData();
516c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com    }
526c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
531e17a681ee8e5566384ead1588f6e6c4c8aa333aroubert@google.com    @Override
542c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    public AddressVerificationNodeData get(String key) {
5537ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        JsoMap jso = mCacheData.getObj(key);
56d0fa5a00b37310cb2d0b3eb4b8282a52ceeca5c7roubert@google.com        if (jso == null) {  // Not cached.
57d0fa5a00b37310cb2d0b3eb4b8282a52ceeca5c7roubert@google.com            fetchDataIfNotAvailable(key);
58d0fa5a00b37310cb2d0b3eb4b8282a52ceeca5c7roubert@google.com            jso = mCacheData.getObj(key);
59d0fa5a00b37310cb2d0b3eb4b8282a52ceeca5c7roubert@google.com        }
602c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (jso != null && isValidDataKey(key)) {
612c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            return createNodeData(jso);
622c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
632c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return null;
646c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com    }
652c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
661e17a681ee8e5566384ead1588f6e6c4c8aa333aroubert@google.com    @Override
672c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    public AddressVerificationNodeData getDefaultData(String key) {
682c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        // root data
692c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (key.split("/").length == 1) {
7037ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            JsoMap jso = mBootstrapMap.get(key);
712c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            if (jso == null || !isValidDataKey(key)) {
722c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                throw new RuntimeException("key " + key + " does not have bootstrap data");
732c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
742c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            return createNodeData(jso);
752c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
762c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
772c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        key = getCountryKey(key);
7837ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        JsoMap jso = mBootstrapMap.get(key);
792c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (jso == null || !isValidDataKey(key)) {
802c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            throw new RuntimeException("key " + key + " does not have bootstrap data");
812c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
822c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return createNodeData(jso);
836c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com    }
846c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
852c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private String getCountryKey(String hierarchyKey) {
862c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (hierarchyKey.split("/").length <= 1) {
872c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            throw new RuntimeException("Cannot get country key with key '" + hierarchyKey + "'");
882c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
892c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (isCountryKey(hierarchyKey)) {
902c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            return hierarchyKey;
912c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
926c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
932c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        String[] parts = hierarchyKey.split("/");
946c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
952c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return new StringBuilder().append(parts[0])
962c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                .append("/")
972c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                .append(parts[1])
982c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                .toString();
992c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    }
1006c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
1012c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private boolean isCountryKey(String hierarchyKey) {
1022c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        Util.checkNotNull(hierarchyKey, "Cannot use null as a key");
1032c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return hierarchyKey.split("/").length == 2;
1042c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    }
1056c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
1066c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
1072c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    /**
1082c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * Returns the contents of the JSON-format string as a map.
1092c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     */
1102c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    protected AddressVerificationNodeData createNodeData(JsoMap jso) {
1112c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        Map<AddressDataKey, String> map =
1122c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                new EnumMap<AddressDataKey, String>(AddressDataKey.class);
1132c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1142c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        JSONArray arr = jso.getKeys();
1152c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        for (int i = 0; i < arr.length(); i++) {
1162c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            try {
1172c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                AddressDataKey key = AddressDataKey.get(arr.getString(i));
1182c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1192a2386e14357a672090d07daf6e2316d2dd34b88lararennie@google.com                if (key == null) {
1202a2386e14357a672090d07daf6e2316d2dd34b88lararennie@google.com                    // Not all keys are supported by Android, so we continue if we encounter one
1212a2386e14357a672090d07daf6e2316d2dd34b88lararennie@google.com                    // that is not used.
1222a2386e14357a672090d07daf6e2316d2dd34b88lararennie@google.com                    continue;
1232a2386e14357a672090d07daf6e2316d2dd34b88lararennie@google.com                }
1242c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1252c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                String value = jso.get(key.toString().toLowerCase());
1262c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                map.put(key, value);
1272c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            } catch (JSONException e) {
1282c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // This should not happen - we should not be fetching a key from outside the bounds
1292c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // of the array.
1302c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
1312c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
1326c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
1332c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return new AddressVerificationNodeData(map);
1346c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com    }
1356c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
1362c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    /**
1372c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * We can be initialized with the full set of address information, but validation only uses info
1382c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * prefixed with "data" (in particular, no info prefixed with "examples").
1392c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     */
1402c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private boolean isValidDataKey(String key) {
1412c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        return key.startsWith("data");
1426c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com    }
1436c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com
1442c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    /**
1452c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * Initializes regionalData structure based on property file.
1466c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com     */
1472c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private void buildRegionalData() {
1482c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        StringBuilder countries = new StringBuilder();
1492c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1502c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        for (String countryCode : RegionDataConstants.getCountryFormatMap().keySet()) {
1512c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            countries.append(countryCode + "~");
1522c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            String json = RegionDataConstants.getCountryFormatMap().get(countryCode);
1532c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            JsoMap jso = null;
1542c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            try {
1552c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                jso = JsoMap.buildJsoMap(json);
1562c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            } catch (JSONException e) {
1572c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // Ignore.
1582c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
1592c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1602c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            AddressData data = new AddressData.Builder().setCountry(countryCode).build();
1612c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            LookupKey key = new LookupKey.Builder(KeyType.DATA).setAddressData(data).build();
16237ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            mBootstrapMap.put(key.toString(), jso);
1632c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
1642c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        countries.setLength(countries.length() - 1);
1652c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1662c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        // TODO: this is messy. do we have better ways to do it?
1672c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        /* Creates verification data for key="data". This will be used for the
1682c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com         * root FieldVerifier.
1692c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com         */
1702c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        String str = "{\"id\":\"data\",\"" +
1712c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                AddressDataKey.COUNTRIES.toString().toLowerCase() +
1722c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                "\": \"" + countries.toString() + "\"}";
1732c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        JsoMap jsoData = null;
1742c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        try {
1752c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            jsoData = JsoMap.buildJsoMap(str);
1762c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        } catch (JSONException e) {
1772c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            // Ignore.
1782c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
17937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mBootstrapMap.put("data", jsoData);
18056493344f6b48d9ae2a2fea8e2a953f33b1e00d8roubert@google.com    }
1812c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
1822c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    /**
1832c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * Fetches data from remote server if it is not cached yet.
1842c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     *
1852c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * @param key The key for data that being requested. Key can be either a data key (starts with
1862c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     *            "data") or example key (starts with "examples")
1872c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     */
1882c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private void fetchDataIfNotAvailable(String key) {
18937ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        JsoMap jso = mCacheData.getObj(key);
1902c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        if (jso == null) {
1912c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            // If there is bootstrap data for the key, pass the data to fetchDynamicData
19237ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            JsoMap regionalData = mBootstrapMap.get(key);
193d0fa5a00b37310cb2d0b3eb4b8282a52ceeca5c7roubert@google.com            NotifyingListener listener = new NotifyingListener(this);
194f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com            // If the key was invalid, we don't want to attempt to fetch it.
195f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com            if (LookupKey.hasValidKeyPrefix(key)) {
196d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                LookupKey lookupKey = new LookupKey.Builder(key).build();
197d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                mCacheData.fetchDynamicData(lookupKey, regionalData, listener);
198f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com                try {
199f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com                    listener.waitLoadingEnd();
200d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                    // Check to see if there is data for this key now.
201d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                    if (mCacheData.getObj(key) == null && isCountryKey(key)) {
202d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                        // If not, see if there is data in RegionDataConstants.
2034846244b97f572c866738a8eef50e530cd73257ashaopengjia@google.com                        Log.i(TAG, "Server failure: looking up key in region data constants.");
204d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                        mCacheData.getFromRegionDataConstants(lookupKey);
205d07313666e6455c4b63f965ac6a3697f7a9573e5lararennie@google.com                    }
206f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com                } catch (InterruptedException e) {
207f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com                    throw new RuntimeException(e);
208f3a3b6c5a1dce4d0e718146724ecfa1070900dablararennie@google.com                }
209d0fa5a00b37310cb2d0b3eb4b8282a52ceeca5c7roubert@google.com            }
2102c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        }
2116c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com    }
2122c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2132c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    public void requestData(LookupKey key, DataLoadListener listener) {
2142c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        Util.checkNotNull(key, "Null lookup key not allowed");
21537ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        JsoMap regionalData = mBootstrapMap.get(key.toString());
21637ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mCacheData.fetchDynamicData(key, regionalData, listener);
217579db66046292a306a96dc478fa17da605fdda6croubert@google.com    }
2182c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2192c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    /**
2202c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * Fetches all data for the specified country from the remote server.
2212c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     */
2222c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    public void prefetchCountry(String country, DataLoadListener listener) {
2232c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        String key = "data/" + country;
2242c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        Set<RecursiveLoader> loaders = new HashSet<RecursiveLoader>();
225e5107b05ab6849c4c21f82ba7164742c18c9d1c1roubert@google.com        listener.dataLoadingBegin();
22637ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com        mCacheData.fetchDynamicData(
2272c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                new LookupKey.Builder(key).build(),
2282c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                null,
2292c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                new RecursiveLoader(key, loaders, listener));
230579db66046292a306a96dc478fa17da605fdda6croubert@google.com    }
231579db66046292a306a96dc478fa17da605fdda6croubert@google.com
2322c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    /**
2332c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     * A helper class to recursively load all sub keys using fetchDynamicData().
2342c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com     */
2352c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com    private class RecursiveLoader implements DataLoadListener {
2362c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2372c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        private final String key;
2382c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2392c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        private final Set<RecursiveLoader> loaders;
2402c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2412c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        private final DataLoadListener listener;
2422c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2432c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        public RecursiveLoader(String key, Set<RecursiveLoader> loaders,
2442c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                DataLoadListener listener) {
2452c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            this.key = key;
2462c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            this.loaders = loaders;
2472c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            this.listener = listener;
2482c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2492c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            synchronized (loaders) {
2502c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                loaders.add(this);
2512c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
252579db66046292a306a96dc478fa17da605fdda6croubert@google.com        }
2532c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2541e17a681ee8e5566384ead1588f6e6c4c8aa333aroubert@google.com        @Override
2552c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        public void dataLoadingBegin() {
256579db66046292a306a96dc478fa17da605fdda6croubert@google.com        }
2572c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2581e17a681ee8e5566384ead1588f6e6c4c8aa333aroubert@google.com        @Override
2592c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com        public void dataLoadingEnd() {
260120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com            final String subKeys = AddressDataKey.SUB_KEYS.name().toLowerCase();
261120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com            final String subMores = AddressDataKey.SUB_MORES.name().toLowerCase();
2622c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
26337ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com            JsoMap map = mCacheData.getObj(key);
2642c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
265120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com            if (map.containsKey(subMores)) {
2662c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                // This key could have sub keys.
2672c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                String[] mores = {};
2682c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                String[] keys = {};
2692c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
270120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com                mores = map.get(subMores).split("~");
2712c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
272120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com                if (map.containsKey(subKeys)) {
273120958045abaca8335af9870d48cb29c940d1fe8lararennie@google.com                    keys = map.get(subKeys).split("~");
2742c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2752c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2762c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                if (mores.length != keys.length) {  // This should never happen.
2772c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    throw new IndexOutOfBoundsException("mores.length != keys.length");
2782c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2792c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2802c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                for (int i = 0; i < mores.length; i++) {
2812c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    if (mores[i].equalsIgnoreCase("true")) {
2822c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                        // This key should have sub keys.
2832c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                        String subKey = key + "/" + keys[i];
28437ea1c8bb9bf0ee18a9ce7412ace03885098e348lararennie@google.com                        mCacheData.fetchDynamicData(
2852c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                                new LookupKey.Builder(subKey).build(),
2862c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                                null,
2872c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                                new RecursiveLoader(subKey, loaders, listener));
2882c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    }
2892c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2902c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
2912c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com
2922c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            synchronized (loaders) {
2932c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                loaders.remove(this);
2942c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                if (loaders.isEmpty()) {
2952c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                    listener.dataLoadingEnd();
2962c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com                }
2972c25a6f4922225b619b0e389befde8b941e78834jeanine@google.com            }
298579db66046292a306a96dc478fa17da605fdda6croubert@google.com        }
299579db66046292a306a96dc478fa17da605fdda6croubert@google.com    }
3006c0b10861ff77331bd5a20d527872ad55a5043e1lararennie@google.com}
301