blob: bd47fdd92ef2aca0d70d59238e0d5eb766599400 [file] [log] [blame]
/*
* 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 java.util.HashMap;
import java.util.Map;
/**
* A simple data structure for international postal addresses.
*
* Addresses may seem simple, but even within the US there are many quirks
* (hyphenated street addresses, etc.), and internationally addresses
* vary a great deal. The most sane and complete in many ways is the OASIS
* "extensible Address Language", xAL, which is a published and documented
* XML schema:
*
* http://www.oasis-open.org/committees/ciq/download.shtml
*
* We have not represented all the fields, but the intent is that if you need
* to add something, you should follow the OASIS standard.
*
* An example address:
* <p>postalCountry: US</p>
* <p>addressLine1: 1098 Alta Ave</p>
* <p>addressLine1:</p>
* <p>addressLine3:</p>
* <p>adminstrativeArea: CA</p>
* <p>locality: Mountain View</p>
* <p>dependentLocality:</p>
* <p>postalCode: 94043</p>
* <p>sortingCode:</p>
* <p>organization: Google</p>
* <p>recipient: Chen-Kang Yang</p>
* <p>language code: en</p>
*
* Note that sub-administrative area is NOT used in Address Widget.
* Sub-administrative Area is second-level administrative subdivision of
* this country. For examples: US county, IT province, UK county. This level
* of geo information is not required to fill out address form, therefore is
* neglected.
*
* All values stored in this class are trimmed. Also, if you try to set a field
* with an empty string or a string consists of only spaces, it will not be set.
*/
public class AddressData {
// ISO 3166-1-alpha-2 country code (two letter codes, as used in DNS)
// For example, "US" for United States.
// (Note: Use "GB", not "UK", for Great Britain)
private final String postalCountry;
// street street, line 1
private final String addressLine1;
// street street, line 2
private final String addressLine2;
// Top-level administrative subdivision of this country.
// Examples: US state, IT region, UK constituent nation, JP prefecture.
private final String administrativeArea;
// Locality. A fuzzy term, but it generally refers to
// the city/town portion of an address. In regions of the world where
// localities are not well defined or do not fit into this structure well
// (for example, Japan and China), leave locality_name empty and use
// address_line.
// Examples: US city, IT comune, UK post town.
private final String locality;
// Dependent locality or sublocality. Used for UK dependent localities,
// or neighborhoods or boroughs in other locations. If trying to
// represent a UK double-dependent locality, include both the
// double-dependent locality and the dependent locality in this field,
// e.g. "Whaley, Langwith".
private final String dependentLocality;
// Postal Code. values are frequently alphanumeric.
// Examples: "94043", "94043-1351", "SW1W", "SW1W 9TQ".
private final String postalCode;
// Sorting code - use is very country-specific.
// This corresponds to the SortingCode sub-element of the xAL
// PostalServiceElements element.
// Examples: FR CEDEX.
private final String sortingCode;
// The firm or organization. This goes at a finer granularity than
// address_lines in the address. Omit if not needed.
private final String organization;
// The recipient. This goes at a finer granularity than address_lines
// in the address. Not present in xAL. Omit if not needed.
private final String recipient;
// Language code of the address. Can be set to null. See its getter and setter
// for more information.
private final String languageCode;
/**
* Use {@link Builder} to create instances.
*/
private AddressData(Builder builder) {
postalCountry = builder.values.get(AddressField.COUNTRY);
administrativeArea = builder.values.get(AddressField.ADMIN_AREA);
locality = builder.values.get(AddressField.LOCALITY);
dependentLocality = builder.values.get(AddressField.DEPENDENT_LOCALITY);
postalCode = builder.values.get(AddressField.POSTAL_CODE);
sortingCode = builder.values.get(AddressField.SORTING_CODE);
organization = builder.values.get(AddressField.ORGANIZATION);
recipient = builder.values.get(AddressField.RECIPIENT);
String line1 = builder.values.get(AddressField.ADDRESS_LINE_1);
String line2 = builder.values.get(AddressField.ADDRESS_LINE_2);
languageCode = builder.languageCode;
// Normalize address lines.
if (line1 == null && line2 != null) {
line1 = line2;
line2 = null;
}
addressLine1 = line1;
addressLine2 = line2;
}
/**
* Returns the postal country.
*
* <p>The returned value is not user-presentable. For example,
* {@code getPostalCountry()} may return {@code "GB"}, while
* addresses in Great Britain should be displayed using "UK".
*/
public String getPostalCountry() {
return postalCountry;
}
public String getAddressLine1() {
return addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
/**
* Returns the top-level administrative subdivision of this country.
* Different postal countries use different names to refer to their
* administrative areas. For example, this is called "state" in the United
* States, "region" in Italy, "constituent nation" in Great Britain, or
* "prefecture" in Japan.
*/
public String getAdministrativeArea() {
return administrativeArea;
}
/**
* Returns the locality. The usage of this field varies by region, but it
* generally refers to the "city" or "town" of the address. Some regions do
* not use this field; their address lines are sufficient to locate an address
* within a sub-administrative area.
* For example, this is called "city" in the United States, "comune" in
* Italy, or "post town" in Great Britain.
*/
public String getLocality() {
return locality;
}
/**
* Returns the dependent locality.
*
* <p>This is used for Great Britain dependent localities, or neighborhoods
* or boroughs in other locations.
*
* <p>In cases such as Great Britain, this field may contain a
* double-dependent locality, such as "Whaley, Langwith".
*/
public String getDependentLocality() {
return dependentLocality;
}
/**
* Returns the firm or organization.
*/
public String getOrganization() {
return organization;
}
/**
* Returns the recipient. Examples: "Jesse Wilson" or
* "Jesse Wilson c/o Apurva Mathad".
*/
public String getRecipient() {
return recipient;
}
/**
* Returns the country-specific postal code. Examples: "94043", "94043-1351",
* "SW1W", "SW1W 9TQ".
*/
public String getPostalCode() {
return postalCode;
}
/**
* Returns the country-specific sorting code. For example, the
* <a href="http://en.wikipedia.org/wiki/List_of_postal_codes_in_France">
* French CEDEX</a>
*/
public String getSortingCode() {
return sortingCode;
}
public String getFieldValue(AddressField field) {
switch (field) {
case COUNTRY:
return postalCountry;
case ADMIN_AREA:
return administrativeArea;
case LOCALITY:
return locality;
case DEPENDENT_LOCALITY:
return dependentLocality;
case POSTAL_CODE:
return postalCode;
case SORTING_CODE:
return sortingCode;
case ADDRESS_LINE_1:
return addressLine1;
case ADDRESS_LINE_2:
return addressLine2;
case ORGANIZATION:
return organization;
case RECIPIENT:
return recipient;
default:
throw new IllegalArgumentException("unrecognized key: " + field);
}
}
/**
* Returns the language of the text of this address. Languages are used to
* guide how the address is
* <a href="http://en.wikipedia.org/wiki/Mailing_address_format_by_country">
* formatted for display</a>. The same address may have different
* {@link AddressData} representations in different languages. For
* example, the French name of "New Mexico" is "Nouveau-Mexique".
*/
public String getLanguageCode() {
return languageCode;
}
/**
* Builder for AddressData
*/
public static class Builder {
private final Map<AddressField, String> values;
private String languageCode = null;
public Builder() {
values = new HashMap<AddressField, String>();
}
/**
* A constructor that sets address field with input data. Street fields will be normalized in
* the process. I.e., after copy, there will not be any empty street line in front of non-empty
* ones. For example, if input data's street line 1 is null but street line 2 has a value, this
* method will copy street line 2's value and set it to street line 1.
*/
public Builder(AddressData addr) {
values = new HashMap<AddressField, String>();
set(addr);
}
public Builder setCountry(String value) {
return set(AddressField.COUNTRY, value);
}
public Builder setAdminArea(String value) {
return set(AddressField.ADMIN_AREA, value);
}
public Builder setLocality(String value) {
return set(AddressField.LOCALITY, value);
}
public Builder setDependentLocality(String value) {
return set(AddressField.DEPENDENT_LOCALITY, value);
}
public Builder setPostalCode(String value) {
return set(AddressField.POSTAL_CODE, value);
}
public Builder setSortingCode(String value) {
return set(AddressField.SORTING_CODE, value);
}
/**
* Sets the language code.
*
* @param languageCode the language to use, or {@code null} for no specified
* language.
*/
public Builder setLanguageCode(String languageCode) {
this.languageCode = languageCode;
return this;
}
/**
* Sets three street lines from a single street string. The input street string can contain new
* lines. Street string will be trimmed so that the it always set line 1's value first, and then
* follow by line 2. If the input string contains more than two lines, extra new lines will be
* discarded.
*
* <p>
* Example: Input " \n \n1600 Amphitheatre Ave\n\nRoom 122" will set the
* following values:<br/>
* line 1: 1600 Amphitheatre Ave<br/>
* line 2: Room 122<br/>
* </p>
* @param value a street string
*/
public Builder setAddress(String value) {
if (value == null || value.length() == 0) {
return this;
}
int n = 1;
for (String v : value.split("\n")) {
v = v.trim();
if (v.length() > 0 && n == 1) {
setAddressLine1(v);
n++;
} else if (v.length() > 0 && n == 2) {
setAddressLine2(v);
break;
}
// There should never be more than 2 addresslines, if a third or more line
// occurs, it will be dropped.
}
return this;
}
/**
* Sets address by copying from input address data. Street fields will be normalized in the
* process. I.e., after copy, there will not be any empty street line in front of non-empty
* ones. For example, if input data's street line 1 is null but street line 2 has a value, this
* method will copy street line 2's value and set it to street line 1.
*/
public Builder set(AddressData data) {
values.clear();
StringBuilder addressLines = new StringBuilder();
if (data.getFieldValue(AddressField.ADDRESS_LINE_1) != null) {
addressLines.append(data.getFieldValue(AddressField.ADDRESS_LINE_1));
}
if (data.getFieldValue(AddressField.ADDRESS_LINE_2) != null) {
addressLines.append(data.getFieldValue(AddressField.ADDRESS_LINE_2));
}
setAddress(addressLines.toString());
for (AddressField addressField : AddressField.values()) {
if (addressField == AddressField.STREET_ADDRESS ||
(addressField.toString().startsWith("ADDRESS_LINE"))) {
continue; // Do nothing.
} else {
set(addressField, data.getFieldValue(addressField));
}
}
setLanguageCode(data.getLanguageCode());
return this;
}
public Builder setAddressLine1(String value) {
return set(AddressField.ADDRESS_LINE_1, value);
}
public Builder setAddressLine2(String value) {
return set(AddressField.ADDRESS_LINE_2, value);
}
public Builder setOrganization(String value) {
return set(AddressField.ORGANIZATION, value);
}
public Builder setRecipient(String value) {
return set(AddressField.RECIPIENT, value);
}
/**
* Sets an address field with the specified value. If the value is empty
* (a null string, empty string, or a string that contains only spaces), the
* original value associated with the field will be removed.
*/
public Builder set(AddressField field, String value) {
if (value == null || value.length() == 0) {
values.remove(field);
} else {
values.put(field, value.trim());
}
return this;
}
public AddressData build() {
return new AddressData(this);
}
}
}