Home | History | Annotate | Download | only in addressinput
      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 substr : getFormatSubStrings(scriptType, regionCode)) {
    159             if (substr.equals(NEW_LINE)) {
    160                 String normalizedStr = removeAllRedundantSpaces(currentLine.toString());
    161                 if (normalizedStr.length() > 0) {
    162                     lines.add(normalizedStr);
    163                     currentLine.setLength(0);
    164                 }
    165             } else if (substr.startsWith("%")) {
    166                 char c = substr.charAt(1);
    167                 AddressField field = AddressField.of(c);
    168                 Util.checkNotNull(field, "null address field for character " + c);
    169 
    170                 String value = null;
    171                 switch (field) {
    172                     case STREET_ADDRESS:
    173                         value = Util.joinAndSkipNulls("\n",
    174                                 address.getAddressLine1(),
    175                                 address.getAddressLine2());
    176                         break;
    177                     case COUNTRY:
    178                         // Country name is treated separately.
    179                         break;
    180                     case ADMIN_AREA:
    181                         value = address.getAdministrativeArea();
    182                         break;
    183                     case LOCALITY:
    184                         value = address.getLocality();
    185                         break;
    186                     case DEPENDENT_LOCALITY:
    187                         value = address.getDependentLocality();
    188                         break;
    189                     case RECIPIENT:
    190                         value = address.getRecipient();
    191                         break;
    192                     case ORGANIZATION:
    193                         value = address.getOrganization();
    194                         break;
    195                     case POSTAL_CODE:
    196                         value = address.getPostalCode();
    197                         break;
    198                     default:
    199                         break;
    200                 }
    201 
    202                 if (value != null) {
    203                     currentLine.append(value);
    204                 }
    205             } else {
    206                 currentLine.append(substr);
    207             }
    208         }
    209         String normalizedStr = removeAllRedundantSpaces(currentLine.toString());
    210         if (normalizedStr.length() > 0) {
    211             lines.add(normalizedStr);
    212         }
    213         return lines;
    214     }
    215 
    216     /**
    217      * Tokenizes the format string and returns the token string list. "%" is treated as an escape
    218      * character. So for example "%n%a%nxyz" will be split into "%n", "%a", "%n", "x", "y", and "z".
    219      * Escaped tokens correspond to either new line or address fields.
    220      */
    221     private List<String> getFormatSubStrings(ScriptType scriptType, String regionCode) {
    222         String formatString = getFormatString(scriptType, regionCode);
    223         List<String> parts = new ArrayList<String>();
    224 
    225         boolean escaped = false;
    226         for (char c : formatString.toCharArray()) {
    227             if (escaped) {
    228                 escaped = false;
    229                 if (NEW_LINE.equals("%" + c)) {
    230                     parts.add(NEW_LINE);
    231                 } else {
    232                     Util.checkNotNull(AddressField.of(c), "Unrecognized character '" + c
    233                             + "' in format pattern: " + formatString);
    234                     parts.add("%" + c);
    235                 }
    236             } else if (c == '%') {
    237                 escaped = true;
    238             } else {
    239                 parts.add(c + "");
    240             }
    241         }
    242         return parts;
    243     }
    244 
    245     private String removeAllRedundantSpaces(String str) {
    246         str = str.trim();
    247         str = str.replaceAll(" +", " ");
    248         return str;
    249     }
    250 
    251     private String getFormatString(ScriptType scriptType, String regionCode) {
    252         String format = (scriptType == ScriptType.LOCAL)
    253                 ? getJsonValue(regionCode, AddressDataKey.FMT)
    254                 : getJsonValue(regionCode, AddressDataKey.LFMT);
    255         if (format == null) {
    256             format = getJsonValue("ZZ", AddressDataKey.FMT);
    257         }
    258         return format;
    259     }
    260 
    261     private String getJsonValue(String regionCode, AddressDataKey key) {
    262         Util.checkNotNull(regionCode);
    263         String jsonString = RegionDataConstants.getCountryFormatMap().get(regionCode);
    264         Util.checkNotNull(jsonString, "no json data for region code " + regionCode);
    265 
    266         try {
    267             JSONObject jsonObj = new JSONObject(new JSONTokener(jsonString));
    268             if (!jsonObj.has(key.name().toLowerCase())) {
    269                 // Key not found. Return null.
    270                 return null;
    271             }
    272             // Gets the string for this key.
    273             String parsedJsonString = jsonObj.getString(key.name().toLowerCase());
    274             return parsedJsonString;
    275         } catch (JSONException e) {
    276             throw new RuntimeException("Invalid json for region code " + regionCode
    277                     + ": " + jsonString);
    278         }
    279     }
    280 }
    281