Java tutorial
/* * Copyright (C) 2010 Google Inc. * * 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.android.i18n.addressinput; import com.android.i18n.addressinput.LookupKey.KeyType; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Access point for the cached address verification data. The data contained here will mainly be * used to build {@link FieldVerifier}'s. This class is implemented as a singleton. */ public class ClientData implements DataSource { private static final String TAG = "ClientData"; /** * Data to bootstrap the process. The data are all regional (country level) * data. Keys are like "data/US/CA" */ private final Map<String, JsoMap> mBootstrapMap = new HashMap<String, JsoMap>(); private CacheData mCacheData; public ClientData(CacheData cacheData) { this.mCacheData = cacheData; buildRegionalData(); } @Override public AddressVerificationNodeData get(String key) { JsoMap jso = mCacheData.getObj(key); if (jso == null) { // Not cached. fetchDataIfNotAvailable(key); jso = mCacheData.getObj(key); } if (jso != null && isValidDataKey(key)) { return createNodeData(jso); } return null; } @Override public AddressVerificationNodeData getDefaultData(String key) { // root data if (key.split("/").length == 1) { JsoMap jso = mBootstrapMap.get(key); if (jso == null || !isValidDataKey(key)) { throw new RuntimeException("key " + key + " does not have bootstrap data"); } return createNodeData(jso); } key = getCountryKey(key); JsoMap jso = mBootstrapMap.get(key); if (jso == null || !isValidDataKey(key)) { throw new RuntimeException("key " + key + " does not have bootstrap data"); } return createNodeData(jso); } private String getCountryKey(String hierarchyKey) { if (hierarchyKey.split("/").length <= 1) { throw new RuntimeException("Cannot get country key with key '" + hierarchyKey + "'"); } if (isCountryKey(hierarchyKey)) { return hierarchyKey; } String[] parts = hierarchyKey.split("/"); return new StringBuilder().append(parts[0]).append("/").append(parts[1]).toString(); } private boolean isCountryKey(String hierarchyKey) { Util.checkNotNull(hierarchyKey, "Cannot use null as a key"); return hierarchyKey.split("/").length == 2; } /** * Returns the contents of the JSON-format string as a map. */ protected AddressVerificationNodeData createNodeData(JsoMap jso) { Map<AddressDataKey, String> map = new EnumMap<AddressDataKey, String>(AddressDataKey.class); JSONArray arr = jso.getKeys(); for (int i = 0; i < arr.length(); i++) { try { AddressDataKey key = AddressDataKey.get(arr.getString(i)); if (key == null) { // Not all keys are supported by Android, so we continue if we encounter one // that is not used. continue; } String value = jso.get(key.toString().toLowerCase()); map.put(key, value); } catch (JSONException e) { // This should not happen - we should not be fetching a key from outside the bounds // of the array. } } return new AddressVerificationNodeData(map); } /** * We can be initialized with the full set of address information, but validation only uses info * prefixed with "data" (in particular, no info prefixed with "examples"). */ private boolean isValidDataKey(String key) { return key.startsWith("data"); } /** * Initializes regionalData structure based on property file. */ private void buildRegionalData() { StringBuilder countries = new StringBuilder(); for (String countryCode : RegionDataConstants.getCountryFormatMap().keySet()) { countries.append(countryCode + "~"); String json = RegionDataConstants.getCountryFormatMap().get(countryCode); JsoMap jso = null; try { jso = JsoMap.buildJsoMap(json); } catch (JSONException e) { // Ignore. } AddressData data = new AddressData.Builder().setCountry(countryCode).build(); LookupKey key = new LookupKey.Builder(KeyType.DATA).setAddressData(data).build(); mBootstrapMap.put(key.toString(), jso); } countries.setLength(countries.length() - 1); // TODO: this is messy. do we have better ways to do it? /* Creates verification data for key="data". This will be used for the * root FieldVerifier. */ String str = "{\"id\":\"data\",\"" + AddressDataKey.COUNTRIES.toString().toLowerCase() + "\": \"" + countries.toString() + "\"}"; JsoMap jsoData = null; try { jsoData = JsoMap.buildJsoMap(str); } catch (JSONException e) { // Ignore. } mBootstrapMap.put("data", jsoData); } /** * Fetches data from remote server if it is not cached yet. * * @param key The key for data that being requested. Key can be either a data key (starts with * "data") or example key (starts with "examples") */ private void fetchDataIfNotAvailable(String key) { JsoMap jso = mCacheData.getObj(key); if (jso == null) { // If there is bootstrap data for the key, pass the data to fetchDynamicData JsoMap regionalData = mBootstrapMap.get(key); NotifyingListener listener = new NotifyingListener(this); // If the key was invalid, we don't want to attempt to fetch it. if (LookupKey.hasValidKeyPrefix(key)) { LookupKey lookupKey = new LookupKey.Builder(key).build(); mCacheData.fetchDynamicData(lookupKey, regionalData, listener); try { listener.waitLoadingEnd(); // Check to see if there is data for this key now. if (mCacheData.getObj(key) == null && isCountryKey(key)) { // If not, see if there is data in RegionDataConstants. Log.i(TAG, "Server failure: looking up key in region data constants."); mCacheData.getFromRegionDataConstants(lookupKey); } } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public void requestData(LookupKey key, DataLoadListener listener) { Util.checkNotNull(key, "Null lookup key not allowed"); JsoMap regionalData = mBootstrapMap.get(key.toString()); mCacheData.fetchDynamicData(key, regionalData, listener); } /** * Fetches all data for the specified country from the remote server. */ public void prefetchCountry(String country, DataLoadListener listener) { String key = "data/" + country; Set<RecursiveLoader> loaders = new HashSet<RecursiveLoader>(); listener.dataLoadingBegin(); mCacheData.fetchDynamicData(new LookupKey.Builder(key).build(), null, new RecursiveLoader(key, loaders, listener)); } /** * A helper class to recursively load all sub keys using fetchDynamicData(). */ private class RecursiveLoader implements DataLoadListener { private final String key; private final Set<RecursiveLoader> loaders; private final DataLoadListener listener; public RecursiveLoader(String key, Set<RecursiveLoader> loaders, DataLoadListener listener) { this.key = key; this.loaders = loaders; this.listener = listener; synchronized (loaders) { loaders.add(this); } } @Override public void dataLoadingBegin() { } @Override public void dataLoadingEnd() { final String subKeys = AddressDataKey.SUB_KEYS.name().toLowerCase(); final String subMores = AddressDataKey.SUB_MORES.name().toLowerCase(); JsoMap map = mCacheData.getObj(key); if (map.containsKey(subMores)) { // This key could have sub keys. String[] mores = {}; String[] keys = {}; mores = map.get(subMores).split("~"); if (map.containsKey(subKeys)) { keys = map.get(subKeys).split("~"); } if (mores.length != keys.length) { // This should never happen. throw new IndexOutOfBoundsException("mores.length != keys.length"); } for (int i = 0; i < mores.length; i++) { if (mores[i].equalsIgnoreCase("true")) { // This key should have sub keys. String subKey = key + "/" + keys[i]; mCacheData.fetchDynamicData(new LookupKey.Builder(subKey).build(), null, new RecursiveLoader(subKey, loaders, listener)); } } } synchronized (loaders) { loaders.remove(this); if (loaders.isEmpty()) { listener.dataLoadingEnd(); } } } } }