com.android.i18n.addressinput.FormatInterpreter.java Source code

Java tutorial

Introduction

Here is the source code for com.android.i18n.addressinput.FormatInterpreter.java

Source

/*
 * 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.ScriptType;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Address format interpreter. A utility to find address format related info.
 */
class FormatInterpreter {

    private static final String NEW_LINE = "%n";

    private final String mDefaultFormat;

    private final FormOptions mFormOptions;

    /**
     * Creates a new instance of {@link FormatInterpreter}.
     */
    FormatInterpreter(FormOptions options) {
        Util.checkNotNull(RegionDataConstants.getCountryFormatMap(), "null country name map not allowed");
        Util.checkNotNull(options);
        mFormOptions = options;
        mDefaultFormat = getJsonValue("ZZ", AddressDataKey.FMT);
        Util.checkNotNull(mDefaultFormat, "null default format not allowed");
    }

    /**
     * Returns a list of address fields based on the format of {@code regionCode}. Script type is
     * needed because some countries uses different address formats for local/Latin scripts.
     *
     * @param scriptType if {@link ScriptType#LOCAL}, use local format; else use Latin format.
     */
    List<AddressField> getAddressFieldOrder(ScriptType scriptType, String regionCode) {
        Util.checkNotNull(scriptType);
        Util.checkNotNull(regionCode);
        List<AddressField> fieldOrder = new ArrayList<AddressField>();
        for (String substring : getFormatSubStrings(scriptType, regionCode)) {
            // Skips un-escaped characters and new lines.
            if (!substring.matches("%.") || substring.equals(NEW_LINE)) {
                continue;
            }

            AddressField field = AddressField.of(substring.charAt(1));
            fieldOrder.add(field);
        }

        overrideFieldOrder(regionCode, fieldOrder);

        // Uses two address lines instead of street address.
        List<AddressField> finalFieldOrder = new ArrayList<AddressField>();
        for (AddressField field : fieldOrder) {
            if (field == AddressField.STREET_ADDRESS) {
                finalFieldOrder.add(AddressField.ADDRESS_LINE_1);
                finalFieldOrder.add(AddressField.ADDRESS_LINE_2);
            } else {
                finalFieldOrder.add(field);
            }
        }
        return finalFieldOrder;
    }

    /**
     * Returns a list of address fields based on the format of {@code regionCode} -- assuming script
     * type is {@link ScriptType#LOCAL}.
     */
    List<AddressField> getAddressFieldOrder(String regionCode) {
        Util.checkNotNull(regionCode);
        return getAddressFieldOrder(ScriptType.LOCAL, regionCode);
    }

    private void overrideFieldOrder(String regionCode, List<AddressField> fieldOrder) {
        if (mFormOptions.getCustomFieldOrder(regionCode) == null) {
            return;
        }

        // Constructs a hash for overridden field order.
        final Map<AddressField, Integer> fieldPriority = new HashMap<AddressField, Integer>();
        int i = 0;
        for (AddressField field : mFormOptions.getCustomFieldOrder(regionCode)) {
            fieldPriority.put(field, i);
            i++;
        }

        // Finds union of input fields and priority list.
        List<AddressField> union = new ArrayList<AddressField>();
        List<Integer> slots = new ArrayList<Integer>();
        i = 0;
        for (AddressField field : fieldOrder) {
            if (fieldPriority.containsKey(field)) {
                union.add(field);
                slots.add(i);
            }
            i++;
        }

        // Overrides field order with priority list.
        Collections.sort(union, new Comparator<AddressField>() {
            @Override
            public int compare(AddressField o1, AddressField o2) {
                return fieldPriority.get(o1) - fieldPriority.get(o2);
            }
        });

        // Puts reordered fields in slots.
        for (int j = 0; j < union.size(); ++j) {
            fieldOrder.set(slots.get(j), union.get(j));
        }
    }

    /**
     * Gets formatted address. For example,
     *
     * <p> John Doe<br> Dnar Corp<br> 5th St<br> Santa Monica CA 90123 </p>
     *
     * This method does not validate addresses. Also, it will "normalize" the result strings by
     * removing redundant spaces and empty lines.
     */
    List<String> getEnvelopeAddress(AddressData address) {
        Util.checkNotNull(address, "null input address not allowed");
        String regionCode = address.getPostalCountry();

        String lc = address.getLanguageCode();
        ScriptType scriptType = ScriptType.LOCAL;
        if (lc != null) {
            scriptType = Util.isExplicitLatinScript(lc) ? ScriptType.LATIN : ScriptType.LOCAL;
        }

        List<String> lines = new ArrayList<String>();
        StringBuilder currentLine = new StringBuilder();
        for (String formatSymbol : getFormatSubStrings(scriptType, regionCode)) {
            if (formatSymbol.equals(NEW_LINE)) {
                String normalizedStr = removeRedundantSpacesAndLeadingPunctuation(currentLine.toString());
                if (normalizedStr.length() > 0) {
                    lines.add(normalizedStr);
                    currentLine.setLength(0);
                }
            } else if (formatSymbol.startsWith("%")) {
                char c = formatSymbol.charAt(1);
                AddressField field = AddressField.of(c);
                Util.checkNotNull(field, "null address field for character " + c);

                String value = null;
                switch (field) {
                case STREET_ADDRESS:
                    value = Util.joinAndSkipNulls("\n", address.getAddressLine1(), address.getAddressLine2());
                    break;
                case COUNTRY:
                    // Country name is treated separately.
                    break;
                case ADMIN_AREA:
                    value = address.getAdministrativeArea();
                    break;
                case LOCALITY:
                    value = address.getLocality();
                    break;
                case DEPENDENT_LOCALITY:
                    value = address.getDependentLocality();
                    break;
                case RECIPIENT:
                    value = address.getRecipient();
                    break;
                case ORGANIZATION:
                    value = address.getOrganization();
                    break;
                case POSTAL_CODE:
                    value = address.getPostalCode();
                    break;
                default:
                    break;
                }

                if (value != null) {
                    currentLine.append(value);
                }
            } else {
                currentLine.append(formatSymbol);
            }
        }
        String normalizedStr = removeRedundantSpacesAndLeadingPunctuation(currentLine.toString());
        if (normalizedStr.length() > 0) {
            lines.add(normalizedStr);
        }
        return lines;
    }

    /**
     * Tokenizes the format string and returns the token string list. "%" is treated as an escape
     * character. So for example "%n%a%nxyz" will be split into "%n", "%a", "%n", "x", "y", and "z".
     * Escaped tokens correspond to either new line or address fields.
     */
    private List<String> getFormatSubStrings(ScriptType scriptType, String regionCode) {
        String formatString = getFormatString(scriptType, regionCode);
        List<String> parts = new ArrayList<String>();

        boolean escaped = false;
        for (char c : formatString.toCharArray()) {
            if (escaped) {
                escaped = false;
                if (NEW_LINE.equals("%" + c)) {
                    parts.add(NEW_LINE);
                } else {
                    Util.checkNotNull(AddressField.of(c),
                            "Unrecognized character '" + c + "' in format pattern: " + formatString);
                    parts.add("%" + c);
                }
            } else if (c == '%') {
                escaped = true;
            } else {
                parts.add(c + "");
            }
        }
        return parts;
    }

    private static String removeRedundantSpacesAndLeadingPunctuation(String str) {
        // Remove leading commas and other punctuation that might have been added by the formatter
        // in the case of missing data.
        str = str.replaceFirst("^[-,\\s]+", "");
        str = str.trim();
        str = str.replaceAll(" +", " ");
        return str;
    }

    private static String getFormatString(ScriptType scriptType, String regionCode) {
        String format = (scriptType == ScriptType.LOCAL) ? getJsonValue(regionCode, AddressDataKey.FMT)
                : getJsonValue(regionCode, AddressDataKey.LFMT);
        if (format == null) {
            format = getJsonValue("ZZ", AddressDataKey.FMT);
        }
        return format;
    }

    private static String getJsonValue(String regionCode, AddressDataKey key) {
        Util.checkNotNull(regionCode);
        String jsonString = RegionDataConstants.getCountryFormatMap().get(regionCode);
        Util.checkNotNull(jsonString, "no json data for region code " + regionCode);

        try {
            JSONObject jsonObj = new JSONObject(new JSONTokener(jsonString));
            if (!jsonObj.has(key.name().toLowerCase())) {
                // Key not found. Return null.
                return null;
            }
            // Gets the string for this key.
            String parsedJsonString = jsonObj.getString(key.name().toLowerCase());
            return parsedJsonString;
        } catch (JSONException e) {
            throw new RuntimeException("Invalid json for region code " + regionCode + ": " + jsonString);
        }
    }
}