Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / libaddressinput / src / java / src / com / android / i18n / addressinput / ClientData.java
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.i18n.addressinput;
18
19 import com.android.i18n.addressinput.LookupKey.KeyType;
20
21 import android.util.Log;
22
23 import org.json.JSONArray;
24 import org.json.JSONException;
25
26 import java.util.EnumMap;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Map;
30 import java.util.Set;
31
32 /**
33  * Access point for the cached address verification data. The data contained here will mainly be
34  * used to build {@link FieldVerifier}'s. This class is implemented as a singleton.
35  */
36 public class ClientData implements DataSource {
37
38   private static final String TAG = "ClientData";
39
40   /**
41    * Data to bootstrap the process. The data are all regional (country level)
42    * data. Keys are like "data/US/CA"
43    */
44   private final Map<String, JsoMap> bootstrapMap = new HashMap<String, JsoMap>();
45
46   private CacheData cacheData;
47
48   public ClientData(CacheData cacheData) {
49     this.cacheData = cacheData;
50     buildRegionalData();
51   }
52
53   @Override
54   public AddressVerificationNodeData get(String key) {
55     JsoMap jso = cacheData.getObj(key);
56     if (jso == null) {  // Not cached.
57       fetchDataIfNotAvailable(key);
58       jso = cacheData.getObj(key);
59     }
60     if (jso != null && isValidDataKey(key)) {
61       return createNodeData(jso);
62     }
63     return null;
64   }
65
66   @Override
67   public AddressVerificationNodeData getDefaultData(String key) {
68     // root data
69     if (key.split("/").length == 1) {
70       JsoMap jso = bootstrapMap.get(key);
71       if (jso == null || !isValidDataKey(key)) {
72         throw new RuntimeException("key " + key + " does not have bootstrap data");
73       }
74       return createNodeData(jso);
75     }
76
77     key = getCountryKey(key);
78     JsoMap jso = bootstrapMap.get(key);
79     if (jso == null || !isValidDataKey(key)) {
80       throw new RuntimeException("key " + key + " does not have bootstrap data");
81     }
82     return createNodeData(jso);
83   }
84
85   private String getCountryKey(String hierarchyKey) {
86     if (hierarchyKey.split("/").length <= 1) {
87       throw new RuntimeException("Cannot get country key with key '" + hierarchyKey + "'");
88     }
89     if (isCountryKey(hierarchyKey)) {
90       return hierarchyKey;
91     }
92
93     String[] parts = hierarchyKey.split("/");
94
95     return new StringBuilder().append(parts[0])
96         .append("/")
97         .append(parts[1])
98         .toString();
99   }
100
101   private boolean isCountryKey(String hierarchyKey) {
102     Util.checkNotNull(hierarchyKey, "Cannot use null as a key");
103     return hierarchyKey.split("/").length == 2;
104   }
105
106
107   /**
108    * Returns the contents of the JSON-format string as a map.
109    */
110   protected AddressVerificationNodeData createNodeData(JsoMap jso) {
111     Map<AddressDataKey, String> map =
112         new EnumMap<AddressDataKey, String>(AddressDataKey.class);
113
114     JSONArray arr = jso.getKeys();
115     for (int i = 0; i < arr.length(); i++) {
116       try {
117         AddressDataKey key = AddressDataKey.get(arr.getString(i));
118
119         if (key == null) {
120           // Not all keys are supported by Android, so we continue if we encounter one
121           // that is not used.
122           continue;
123         }
124
125         String value = jso.get(key.toString().toLowerCase());
126         map.put(key, value);
127       } catch (JSONException e) {
128         // This should not happen - we should not be fetching a key from outside the bounds
129         // of the array.
130       }
131     }
132
133     return new AddressVerificationNodeData(map);
134   }
135
136   /**
137    * We can be initialized with the full set of address information, but validation only uses info
138    * prefixed with "data" (in particular, no info prefixed with "examples").
139    */
140   private boolean isValidDataKey(String key) {
141     return key.startsWith("data");
142   }
143
144   /**
145    * Initializes regionalData structure based on property file.
146    */
147   private void buildRegionalData() {
148     StringBuilder countries = new StringBuilder();
149
150     for (String countryCode : RegionDataConstants.getCountryFormatMap().keySet()) {
151       countries.append(countryCode + "~");
152       String json = RegionDataConstants.getCountryFormatMap().get(countryCode);
153       JsoMap jso = null;
154       try {
155         jso = JsoMap.buildJsoMap(json);
156       } catch (JSONException e) {
157         // Ignore.
158       }
159
160       AddressData data = new AddressData.Builder().setCountry(countryCode).build();
161       LookupKey key = new LookupKey.Builder(KeyType.DATA).setAddressData(data).build();
162       bootstrapMap.put(key.toString(), jso);
163     }
164     countries.setLength(countries.length() - 1);
165
166     // TODO: this is messy. do we have better ways to do it?
167     /* Creates verification data for key="data". This will be used for the
168      * root FieldVerifier.
169      */
170     String str = "{\"id\":\"data\",\"" +
171         AddressDataKey.COUNTRIES.toString().toLowerCase() +
172         "\": \"" + countries.toString() + "\"}";
173     JsoMap jsoData = null;
174     try {
175       jsoData = JsoMap.buildJsoMap(str);
176     } catch (JSONException e) {
177       // Ignore.
178     }
179     bootstrapMap.put("data", jsoData);
180   }
181
182   /**
183    * Fetches data from remote server if it is not cached yet.
184    *
185    * @param key The key for data that being requested. Key can be either a data key (starts with
186    *            "data") or example key (starts with "examples")
187    */
188   private void fetchDataIfNotAvailable(String key) {
189     JsoMap jso = cacheData.getObj(key);
190     if (jso == null) {
191       // If there is bootstrap data for the key, pass the data to fetchDynamicData
192       JsoMap regionalData = bootstrapMap.get(key);
193       NotifyingListener listener = new NotifyingListener(this);
194       // If the key was invalid, we don't want to attempt to fetch it.
195       if (LookupKey.hasValidKeyPrefix(key)) {
196         LookupKey lookupKey = new LookupKey.Builder(key).build();
197         cacheData.fetchDynamicData(lookupKey, regionalData, listener);
198         try {
199           listener.waitLoadingEnd();
200           // Check to see if there is data for this key now.
201           if (cacheData.getObj(key) == null && isCountryKey(key)) {
202             // If not, see if there is data in RegionDataConstants.
203             Log.i(TAG, "Server failure: looking up key in region data constants.");
204             cacheData.getFromRegionDataConstants(lookupKey);
205           }
206         } catch (InterruptedException e) {
207           throw new RuntimeException(e);
208         }
209       }
210     }
211   }
212
213   public void requestData(LookupKey key, DataLoadListener listener) {
214     Util.checkNotNull(key, "Null lookup key not allowed");
215     JsoMap regionalData = bootstrapMap.get(key.toString());
216     cacheData.fetchDynamicData(key, regionalData, listener);
217   }
218
219   /**
220    * Fetches all data for the specified country from the remote server.
221    */
222   public void prefetchCountry(String country, DataLoadListener listener) {
223     String key = "data/" + country;
224     Set<RecursiveLoader> loaders = new HashSet<RecursiveLoader>();
225     listener.dataLoadingBegin();
226     cacheData.fetchDynamicData(
227         new LookupKey.Builder(key).build(),
228         null,
229         new RecursiveLoader(key, loaders, listener));
230   }
231
232   /**
233    * A helper class to recursively load all sub keys using fetchDynamicData().
234    */
235   private class RecursiveLoader implements DataLoadListener {
236
237     private final String key;
238
239     private final Set<RecursiveLoader> loaders;
240
241     private final DataLoadListener listener;
242
243     public RecursiveLoader(String key, Set<RecursiveLoader> loaders,
244         DataLoadListener listener) {
245       this.key = key;
246       this.loaders = loaders;
247       this.listener = listener;
248
249       synchronized (loaders) {
250         loaders.add(this);
251       }
252     }
253
254     @Override
255     public void dataLoadingBegin() {
256     }
257
258     @Override
259     public void dataLoadingEnd() {
260       final String subKeys = AddressDataKey.SUB_KEYS.name().toLowerCase();
261       final String subMores = AddressDataKey.SUB_MORES.name().toLowerCase();
262
263       JsoMap map = cacheData.getObj(key);
264
265       if (map.containsKey(subMores)) {
266         // This key could have sub keys.
267         String[] mores = map.get(subMores).split("~");
268         String[] keys = {};
269
270         if (map.containsKey(subKeys)) {
271           keys = map.get(subKeys).split("~");
272         }
273
274         if (mores.length != keys.length) {  // This should never happen.
275           throw new IndexOutOfBoundsException("mores.length != keys.length");
276         }
277
278         for (int i = 0; i < mores.length; i++) {
279           if (mores[i].equalsIgnoreCase("true")) {
280             // This key should have sub keys.
281             String subKey = key + "/" + keys[i];
282             cacheData.fetchDynamicData(
283                 new LookupKey.Builder(subKey).build(),
284                 null,
285                 new RecursiveLoader(subKey, loaders, listener));
286           }
287         }
288       }
289
290       synchronized (loaders) {
291         loaders.remove(this);
292         if (loaders.isEmpty()) {
293           listener.dataLoadingEnd();
294         }
295       }
296     }
297   }
298 }