Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / libaddressinput / src / java / src / com / android / i18n / addressinput / FormatInterpreter.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.ScriptType;
20
21 import org.json.JSONException;
22 import org.json.JSONObject;
23 import org.json.JSONTokener;
24
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31
32 /**
33  * Address format interpreter. A utility to find address format related info.
34  */
35 class FormatInterpreter {
36
37     private static final String NEW_LINE = "%n";
38
39     private final String mDefaultFormat;
40
41     private final FormOptions mFormOptions;
42
43     /**
44      * Creates a new instance of {@link FormatInterpreter}.
45      */
46     FormatInterpreter(FormOptions options) {
47         Util.checkNotNull(RegionDataConstants.getCountryFormatMap(),
48                 "null country name map not allowed");
49         Util.checkNotNull(options);
50         mFormOptions = options;
51         mDefaultFormat = getJsonValue("ZZ", AddressDataKey.FMT);
52         Util.checkNotNull(mDefaultFormat, "null default format not allowed");
53     }
54
55     /**
56      * Returns a list of address fields based on the format of {@code regionCode}. Script type is
57      * needed because some countries uses different address formats for local/Latin scripts.
58      *
59      * @param scriptType if {@link ScriptType#LOCAL}, use local format; else use Latin format.
60      */
61     List<AddressField> getAddressFieldOrder(ScriptType scriptType, String regionCode) {
62         Util.checkNotNull(scriptType);
63         Util.checkNotNull(regionCode);
64         List<AddressField> fieldOrder = new ArrayList<AddressField>();
65         for (String substring : getFormatSubStrings(scriptType, regionCode)) {
66             // Skips un-escaped characters and new lines.
67             if (!substring.matches("%.") || substring.equals(NEW_LINE)) {
68                 continue;
69             }
70
71             AddressField field = AddressField.of(substring.charAt(1));
72             fieldOrder.add(field);
73         }
74
75         overrideFieldOrder(regionCode, fieldOrder);
76
77         // Uses two address lines instead of street address.
78         List<AddressField> finalFieldOrder = new ArrayList<AddressField>();
79         for (AddressField field : fieldOrder) {
80             if (field == AddressField.STREET_ADDRESS) {
81                 finalFieldOrder.add(AddressField.ADDRESS_LINE_1);
82                 finalFieldOrder.add(AddressField.ADDRESS_LINE_2);
83             } else {
84                 finalFieldOrder.add(field);
85             }
86         }
87         return finalFieldOrder;
88     }
89
90     /**
91      * Returns a list of address fields based on the format of {@code regionCode} -- assuming script
92      * type is {@link ScriptType#LOCAL}.
93      */
94     List<AddressField> getAddressFieldOrder(String regionCode) {
95         Util.checkNotNull(regionCode);
96         return getAddressFieldOrder(ScriptType.LOCAL, regionCode);
97     }
98
99     private void overrideFieldOrder(String regionCode, List<AddressField> fieldOrder) {
100         if (mFormOptions.getCustomFieldOrder(regionCode) == null) {
101             return;
102         }
103
104         // Constructs a hash for overridden field order.
105         final Map<AddressField, Integer> fieldPriority = new HashMap<AddressField, Integer>();
106         int i = 0;
107         for (AddressField field : mFormOptions.getCustomFieldOrder(regionCode)) {
108             fieldPriority.put(field, i);
109             i++;
110         }
111
112         // Finds union of input fields and priority list.
113         List<AddressField> union = new ArrayList<AddressField>();
114         List<Integer> slots = new ArrayList<Integer>();
115         i = 0;
116         for (AddressField field : fieldOrder) {
117             if (fieldPriority.containsKey(field)) {
118                 union.add(field);
119                 slots.add(i);
120             }
121             i++;
122         }
123
124         // Overrides field order with priority list.
125         Collections.sort(union, new Comparator<AddressField>() {
126             @Override
127             public int compare(AddressField o1, AddressField o2) {
128                 return fieldPriority.get(o1) - fieldPriority.get(o2);
129             }
130         });
131
132         // Puts reordered fields in slots.
133         for (int j = 0; j < union.size(); ++j) {
134             fieldOrder.set(slots.get(j), union.get(j));
135         }
136     }
137
138     /**
139      * Gets formatted address. For example,
140      *
141      * <p> John Doe<br> Dnar Corp<br> 5th St<br> Santa Monica CA 90123 </p>
142      *
143      * This method does not validate addresses. Also, it will "normalize" the result strings by
144      * removing redundant spaces and empty lines.
145      */
146     List<String> getEnvelopeAddress(AddressData address) {
147         Util.checkNotNull(address, "null input address not allowed");
148         String regionCode = address.getPostalCountry();
149
150         String lc = address.getLanguageCode();
151         ScriptType scriptType = ScriptType.LOCAL;
152         if (lc != null) {
153             scriptType = Util.isExplicitLatinScript(lc) ? ScriptType.LATIN : ScriptType.LOCAL;
154         }
155
156         List<String> lines = new ArrayList<String>();
157         StringBuilder currentLine = new StringBuilder();
158         for (String formatSymbol : getFormatSubStrings(scriptType, regionCode)) {
159             if (formatSymbol.equals(NEW_LINE)) {
160                 String normalizedStr =
161                         removeRedundantSpacesAndLeadingPunctuation(currentLine.toString());
162                 if (normalizedStr.length() > 0) {
163                     lines.add(normalizedStr);
164                     currentLine.setLength(0);
165                 }
166             } else if (formatSymbol.startsWith("%")) {
167                 char c = formatSymbol.charAt(1);
168                 AddressField field = AddressField.of(c);
169                 Util.checkNotNull(field, "null address field for character " + c);
170
171                 String value = null;
172                 switch (field) {
173                     case STREET_ADDRESS:
174                         value = Util.joinAndSkipNulls("\n",
175                                 address.getAddressLine1(),
176                                 address.getAddressLine2());
177                         break;
178                     case COUNTRY:
179                         // Country name is treated separately.
180                         break;
181                     case ADMIN_AREA:
182                         value = address.getAdministrativeArea();
183                         break;
184                     case LOCALITY:
185                         value = address.getLocality();
186                         break;
187                     case DEPENDENT_LOCALITY:
188                         value = address.getDependentLocality();
189                         break;
190                     case RECIPIENT:
191                         value = address.getRecipient();
192                         break;
193                     case ORGANIZATION:
194                         value = address.getOrganization();
195                         break;
196                     case POSTAL_CODE:
197                         value = address.getPostalCode();
198                         break;
199                     default:
200                         break;
201                 }
202
203                 if (value != null) {
204                     currentLine.append(value);
205                 }
206             } else {
207                 currentLine.append(formatSymbol);
208             }
209         }
210         String normalizedStr = removeRedundantSpacesAndLeadingPunctuation(currentLine.toString());
211         if (normalizedStr.length() > 0) {
212             lines.add(normalizedStr);
213         }
214         return lines;
215     }
216
217     /**
218      * Tokenizes the format string and returns the token string list. "%" is treated as an escape
219      * character. So for example "%n%a%nxyz" will be split into "%n", "%a", "%n", "x", "y", and "z".
220      * Escaped tokens correspond to either new line or address fields.
221      */
222     private List<String> getFormatSubStrings(ScriptType scriptType, String regionCode) {
223         String formatString = getFormatString(scriptType, regionCode);
224         List<String> parts = new ArrayList<String>();
225
226         boolean escaped = false;
227         for (char c : formatString.toCharArray()) {
228             if (escaped) {
229                 escaped = false;
230                 if (NEW_LINE.equals("%" + c)) {
231                     parts.add(NEW_LINE);
232                 } else {
233                     Util.checkNotNull(AddressField.of(c), "Unrecognized character '" + c
234                             + "' in format pattern: " + formatString);
235                     parts.add("%" + c);
236                 }
237             } else if (c == '%') {
238                 escaped = true;
239             } else {
240                 parts.add(c + "");
241             }
242         }
243         return parts;
244     }
245
246     private static String removeRedundantSpacesAndLeadingPunctuation(String str) {
247         // Remove leading commas and other punctuation that might have been added by the formatter
248         // in the case of missing data.
249         str = str.replaceFirst("^[-,\\s]+", "");
250         str = str.trim();
251         str = str.replaceAll(" +", " ");
252         return str;
253     }
254
255     private static String getFormatString(ScriptType scriptType, String regionCode) {
256         String format = (scriptType == ScriptType.LOCAL)
257                 ? getJsonValue(regionCode, AddressDataKey.FMT)
258                 : getJsonValue(regionCode, AddressDataKey.LFMT);
259         if (format == null) {
260             format = getJsonValue("ZZ", AddressDataKey.FMT);
261         }
262         return format;
263     }
264
265     private static String getJsonValue(String regionCode, AddressDataKey key) {
266         Util.checkNotNull(regionCode);
267         String jsonString = RegionDataConstants.getCountryFormatMap().get(regionCode);
268         Util.checkNotNull(jsonString, "no json data for region code " + regionCode);
269
270         try {
271             JSONObject jsonObj = new JSONObject(new JSONTokener(jsonString));
272             if (!jsonObj.has(key.name().toLowerCase())) {
273                 // Key not found. Return null.
274                 return null;
275             }
276             // Gets the string for this key.
277             String parsedJsonString = jsonObj.getString(key.name().toLowerCase());
278             return parsedJsonString;
279         } catch (JSONException e) {
280             throw new RuntimeException("Invalid json for region code " + regionCode
281                     + ": " + jsonString);
282         }
283     }
284 }