2 * Copyright (C) 2010 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.i18n.addressinput;
19 import com.android.i18n.addressinput.LookupKey.ScriptType;
21 import org.json.JSONException;
22 import org.json.JSONObject;
23 import org.json.JSONTokener;
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;
33 * Address format interpreter. A utility to find address format related info.
35 class FormatInterpreter {
37 private static final String NEW_LINE = "%n";
39 private final String mDefaultFormat;
41 private final FormOptions mFormOptions;
44 * Creates a new instance of {@link FormatInterpreter}.
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");
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.
59 * @param scriptType if {@link ScriptType#LOCAL}, use local format; else use Latin format.
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)) {
71 AddressField field = AddressField.of(substring.charAt(1));
72 fieldOrder.add(field);
75 overrideFieldOrder(regionCode, fieldOrder);
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);
84 finalFieldOrder.add(field);
87 return finalFieldOrder;
91 * Returns a list of address fields based on the format of {@code regionCode} -- assuming script
92 * type is {@link ScriptType#LOCAL}.
94 List<AddressField> getAddressFieldOrder(String regionCode) {
95 Util.checkNotNull(regionCode);
96 return getAddressFieldOrder(ScriptType.LOCAL, regionCode);
99 private void overrideFieldOrder(String regionCode, List<AddressField> fieldOrder) {
100 if (mFormOptions.getCustomFieldOrder(regionCode) == null) {
104 // Constructs a hash for overridden field order.
105 final Map<AddressField, Integer> fieldPriority = new HashMap<AddressField, Integer>();
107 for (AddressField field : mFormOptions.getCustomFieldOrder(regionCode)) {
108 fieldPriority.put(field, i);
112 // Finds union of input fields and priority list.
113 List<AddressField> union = new ArrayList<AddressField>();
114 List<Integer> slots = new ArrayList<Integer>();
116 for (AddressField field : fieldOrder) {
117 if (fieldPriority.containsKey(field)) {
124 // Overrides field order with priority list.
125 Collections.sort(union, new Comparator<AddressField>() {
127 public int compare(AddressField o1, AddressField o2) {
128 return fieldPriority.get(o1) - fieldPriority.get(o2);
132 // Puts reordered fields in slots.
133 for (int j = 0; j < union.size(); ++j) {
134 fieldOrder.set(slots.get(j), union.get(j));
139 * Gets formatted address. For example,
141 * <p> John Doe<br> Dnar Corp<br> 5th St<br> Santa Monica CA 90123 </p>
143 * This method does not validate addresses. Also, it will "normalize" the result strings by
144 * removing redundant spaces and empty lines.
146 List<String> getEnvelopeAddress(AddressData address) {
147 Util.checkNotNull(address, "null input address not allowed");
148 String regionCode = address.getPostalCountry();
150 String lc = address.getLanguageCode();
151 ScriptType scriptType = ScriptType.LOCAL;
153 scriptType = Util.isExplicitLatinScript(lc) ? ScriptType.LATIN : ScriptType.LOCAL;
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);
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);
174 value = Util.joinAndSkipNulls("\n",
175 address.getAddressLine1(),
176 address.getAddressLine2());
179 // Country name is treated separately.
182 value = address.getAdministrativeArea();
185 value = address.getLocality();
187 case DEPENDENT_LOCALITY:
188 value = address.getDependentLocality();
191 value = address.getRecipient();
194 value = address.getOrganization();
197 value = address.getPostalCode();
204 currentLine.append(value);
207 currentLine.append(formatSymbol);
210 String normalizedStr = removeRedundantSpacesAndLeadingPunctuation(currentLine.toString());
211 if (normalizedStr.length() > 0) {
212 lines.add(normalizedStr);
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.
222 private List<String> getFormatSubStrings(ScriptType scriptType, String regionCode) {
223 String formatString = getFormatString(scriptType, regionCode);
224 List<String> parts = new ArrayList<String>();
226 boolean escaped = false;
227 for (char c : formatString.toCharArray()) {
230 if (NEW_LINE.equals("%" + c)) {
233 Util.checkNotNull(AddressField.of(c), "Unrecognized character '" + c
234 + "' in format pattern: " + formatString);
237 } else if (c == '%') {
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]+", "");
251 str = str.replaceAll(" +", " ");
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);
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);
271 JSONObject jsonObj = new JSONObject(new JSONTokener(jsonString));
272 if (!jsonObj.has(key.name().toLowerCase())) {
273 // Key not found. Return null.
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);