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
17package com.android.i18n.addressinput;
18
19import java.util.HashMap;
20import java.util.Map;
21
22/**
23 * A simple data structure for international postal addresses.
24 *
25 * Addresses may seem simple, but even within the US there are many quirks (hyphenated street
26 * addresses, etc.), and internationally addresses vary a great deal. The most sane and complete in
27 * many ways is the OASIS "extensible Address Language", xAL, which is a published and documented
28 * XML schema:
29 *
30 * http://www.oasis-open.org/committees/ciq/download.shtml
31 *
32 * We have not represented all the fields, but the intent is that if you need to add something, you
33 * should follow the OASIS standard.
34 *
35 * An example address:
36 * <p>postalCountry: US</p>
37 * <p>addressLine1: 1098 Alta Ave</p>
38 * <p>addressLine2:</p>
39 * <p>adminstrativeArea: CA</p>
40 * <p>locality: Mountain View</p>
41 * <p>dependentLocality:</p>
42 * <p>postalCode: 94043</p>
43 * <p>sortingCode:</p>
44 * <p>organization: Google</p>
45 * <p>recipient: Chen-Kang Yang</p>
46 * <p>language code: en</p>
47 *
48 * Note that sub-administrative area is NOT used in Address Widget. Sub-administrative Area is
49 * second-level administrative subdivision of this country. For examples: US county, IT province, UK
50 * county. This level of geo information is not required to fill out address form, therefore is
51 * neglected.
52 *
53 * All values stored in this class are trimmed. Also, if you try to set a field with an empty string
54 * or a string consists of only spaces, it will not be set.
55 */
56public class AddressData {
57    // CLDR (Common Locale Data Repository) country code.
58    // For example, "US" for United States.
59    // (Note: Use "GB", not "UK", for Great Britain)
60    private final String mPostalCountry;
61
62    // street street, line 1
63    private final String mAddressLine1;
64
65    // street street, line 2
66    private final String mAddressLine2;
67
68    // Top-level administrative subdivision of this country.
69    // Examples: US state, IT region, UK constituent nation, JP prefecture.
70    private final String mAdministrativeArea;
71
72    // Locality. A fuzzy term, but it generally refers to
73    // the city/town portion of an address.  In regions of the world where
74    // localities are not well defined or do not fit into this structure well
75    // (for example, Japan and China), leave locality empty and use
76    // addressLine1.
77    // Examples: US city, IT comune, UK post town.
78    private final String mLocality;
79
80    // Dependent locality or sublocality.  Used for UK dependent localities,
81    // or neighborhoods or boroughs in other locations.  If trying to
82    // represent a UK double-dependent locality, include both the
83    // double-dependent locality and the dependent locality in this field,
84    // e.g. "Whaley, Langwith".
85    private final String mDependentLocality;
86
87    // Postal Code. values are frequently alphanumeric.
88    // Examples: "94043", "94043-1351", "SW1W", "SW1W 9TQ".
89    private final String mPostalCode;
90
91    // Sorting code - use is very country-specific.
92    // This corresponds to the SortingCode sub-element of the xAL
93    // PostalServiceElements element.
94    // Examples: FR CEDEX.
95    private final String mSortingCode;
96
97    // The firm or organization. This goes at a finer granularity than
98    // address lines in the address. Omit if not needed.
99    private final String mOrganization;
100
101    // The recipient. This goes at a finer granularity than address lines
102    // in the address. Not present in xAL. Omit if not needed.
103    private final String mRecipient;
104
105    // Language code of the address. Can be set to null. See its getter and setter
106    // for more information.
107    private final String mLanguageCode;
108
109    /**
110     * Use {@link Builder} to create instances.
111     */
112    private AddressData(Builder builder) {
113        mPostalCountry = builder.mValues.get(AddressField.COUNTRY);
114        mAdministrativeArea = builder.mValues.get(AddressField.ADMIN_AREA);
115        mLocality = builder.mValues.get(AddressField.LOCALITY);
116        mDependentLocality = builder.mValues.get(AddressField.DEPENDENT_LOCALITY);
117        mPostalCode = builder.mValues.get(AddressField.POSTAL_CODE);
118        mSortingCode = builder.mValues.get(AddressField.SORTING_CODE);
119        mOrganization = builder.mValues.get(AddressField.ORGANIZATION);
120        mRecipient = builder.mValues.get(AddressField.RECIPIENT);
121        mAddressLine1 = builder.mValues.get(AddressField.ADDRESS_LINE_1);
122        mAddressLine2 = builder.mValues.get(AddressField.ADDRESS_LINE_2);
123        mLanguageCode = builder.mLanguage;
124    }
125
126    /**
127     * Returns the postal country.
128     *
129     * <p>The returned value is not user-presentable. For example, {@code getPostalCountry()} may
130     * return {@code "GB"}, while addresses in Great Britain should be displayed using "UK".
131     */
132    public String getPostalCountry() {
133        return mPostalCountry;
134    }
135
136    public String getAddressLine1() {
137        return mAddressLine1;
138    }
139
140    public String getAddressLine2() {
141        return mAddressLine2;
142    }
143
144    /**
145     * Returns the top-level administrative subdivision of this country. Different postal countries
146     * use different names to refer to their administrative areas. For example, this is called
147     * "state" in the United States, "region" in Italy, "constituent nation" in Great Britain, or
148     * "prefecture" in Japan.
149     */
150    public String getAdministrativeArea() {
151        return mAdministrativeArea;
152    }
153
154    /**
155     * Returns the locality. The usage of this field varies by region, but it generally refers to
156     * the "city" or "town" of the address. Some regions do not use this field; their address lines
157     * are sufficient to locate an address within a sub-administrative area. For example, this is
158     * called "city" in the United States, "comune" in Italy, or "post town" in Great Britain.
159     */
160    public String getLocality() {
161        return mLocality;
162    }
163
164    /**
165     * Returns the dependent locality.
166     *
167     * <p>This is used for Great Britain dependent localities, or neighborhoods or boroughs in other
168     * locations.
169     *
170     * <p>In cases such as Great Britain, this field may contain a double-dependent locality, such
171     * as "Whaley, Langwith".
172     */
173    public String getDependentLocality() {
174        return mDependentLocality;
175    }
176
177    /**
178     * Returns the firm or organization.
179     */
180    public String getOrganization() {
181        return mOrganization;
182    }
183
184    /**
185     * Returns the recipient. Examples: "Jesse Wilson" or "Jesse Wilson c/o Apurva Mathad".
186     */
187    public String getRecipient() {
188        return mRecipient;
189    }
190
191    /**
192     * Returns the country-specific postal code. Examples: "94043", "94043-1351", "SW1W",
193     * "SW1W 9TQ".
194     */
195    public String getPostalCode() {
196        return mPostalCode;
197    }
198
199    /**
200     * Returns the country-specific sorting code. For example, the
201     * <a href="http://en.wikipedia.org/wiki/List_of_postal_codes_in_France"> French CEDEX</a>
202     */
203    public String getSortingCode() {
204        return mSortingCode;
205    }
206
207    public String getFieldValue(AddressField field) {
208        switch (field) {
209            case COUNTRY:
210                return mPostalCountry;
211            case ADMIN_AREA:
212                return mAdministrativeArea;
213            case LOCALITY:
214                return mLocality;
215            case DEPENDENT_LOCALITY:
216                return mDependentLocality;
217            case POSTAL_CODE:
218                return mPostalCode;
219            case SORTING_CODE:
220                return mSortingCode;
221            case ADDRESS_LINE_1:
222                return mAddressLine1;
223            case ADDRESS_LINE_2:
224                return mAddressLine2;
225            case ORGANIZATION:
226                return mOrganization;
227            case RECIPIENT:
228                return mRecipient;
229            default:
230                throw new IllegalArgumentException("unrecognized key: " + field);
231        }
232    }
233
234    /**
235     * Returns the language of the text of this address. Languages are used to guide how the address
236     * is <a href="http://en.wikipedia.org/wiki/Mailing_address_format_by_country"> formatted for
237     * display</a>. The same address may have different {@link AddressData} representations in
238     * different languages. For example, the French name of "New Mexico" is "Nouveau-Mexique".
239     */
240    public String getLanguageCode() {
241        return mLanguageCode;
242    }
243
244    /**
245     * Builder for AddressData
246     */
247    public static class Builder {
248
249        private final Map<AddressField, String> mValues;
250
251        private String mLanguage = null;
252
253        public Builder() {
254            mValues = new HashMap<AddressField, String>();
255        }
256
257        /**
258         * A constructor that sets address field with input data. Street fields will be normalized
259         * in the process. I.e., after copy, there will not be any empty street line in front of
260         * non-empty ones. For example, if input data's street line 1 is null but street line 2
261         * has a value, this method will copy street line 2's value and set it to street line 1.
262         */
263        public Builder(AddressData addr) {
264            mValues = new HashMap<AddressField, String>();
265            set(addr);
266        }
267
268        public Builder setCountry(String value) {
269            return set(AddressField.COUNTRY, value);
270        }
271
272        public Builder setAdminArea(String value) {
273            return set(AddressField.ADMIN_AREA, value);
274        }
275
276        public Builder setLocality(String value) {
277            return set(AddressField.LOCALITY, value);
278        }
279
280        public Builder setDependentLocality(String value) {
281            return set(AddressField.DEPENDENT_LOCALITY, value);
282        }
283
284        public Builder setPostalCode(String value) {
285            return set(AddressField.POSTAL_CODE, value);
286        }
287
288        public Builder setSortingCode(String value) {
289            return set(AddressField.SORTING_CODE, value);
290        }
291
292        /**
293         * Sets the language code.
294         *
295         * @param languageCode the language to use, or {@code null} for no specified language.
296         */
297        public Builder setLanguageCode(String languageCode) {
298            this.mLanguage = languageCode;
299            return this;
300        }
301
302        /**
303         * Sets address lines 1 and 2 (if necessary) from a string that may contain multiple lines.
304         *
305         * <p> Example: Input "  \n   \n1600 Amphitheatre Ave\n\nRoom 122" will set the following
306         * values:<br/> line 1: 1600 Amphitheatre Ave<br/> line 2: Room 122<br/> </p>
307         *
308         * @param value a street string
309         */
310        public Builder setAddress(String value) {
311            setAddressLine1(value);
312            return this;
313        }
314
315        /**
316         * Sets address by copying from input address data. Street fields will be normalized in the
317         * process. I.e., after copy, there will not be any empty street lines in front of non-empty
318         * ones. For example, if input data's street line 1 is null but street line 2 has a value,
319         * this method will copy street line 2's value and set it to street line 1.
320         */
321        public Builder set(AddressData data) {
322            mValues.clear();
323            for (AddressField addressField : AddressField.values()) {
324                if (addressField == AddressField.STREET_ADDRESS) {
325                    continue;  // Do nothing.
326                } else {
327                    set(addressField, data.getFieldValue(addressField));
328                }
329            }
330            normalizeAddresses();
331            setLanguageCode(data.getLanguageCode());
332            return this;
333        }
334
335        public Builder setAddressLine1(String value) {
336            return set(AddressField.ADDRESS_LINE_1, value);
337        }
338
339        public Builder setAddressLine2(String value) {
340            return set(AddressField.ADDRESS_LINE_2, value);
341        }
342
343        public Builder setOrganization(String value) {
344            return set(AddressField.ORGANIZATION, value);
345        }
346
347        public Builder setRecipient(String value) {
348            return set(AddressField.RECIPIENT, value);
349        }
350
351        /**
352         * Sets an address field with the specified value. If the value is empty (a null string,
353         * empty string, or a string that contains only spaces), the original value associated with
354         * the field will be removed.
355         */
356        public Builder set(AddressField field, String value) {
357            if (value == null || value.length() == 0) {
358                mValues.remove(field);
359            } else {
360                mValues.put(field, value.trim());
361            }
362            normalizeAddresses();
363            return this;
364        }
365
366        public AddressData build() {
367            return new AddressData(this);
368        }
369
370        /**
371         * Parses content of address line fields.
372         * If address_line_1 is empty, address_line_2 will be used to populate address_line_1 if
373         * possible. If address_line_1 contains a new line, content after the new line will be
374         * saved in address_line_2.
375         */
376        private void normalizeAddresses() {
377            String address1 = mValues.get(AddressField.ADDRESS_LINE_1);
378            String address2 = mValues.get(AddressField.ADDRESS_LINE_2);
379            if (address1 == null || address1.trim().length() == 0) {
380                address1 = address2;
381                address2 = null;
382            }
383            if (address1 != null) {
384                String[] addressLines = address1.split("\n");
385                if (addressLines.length > 1) {
386                    address1 = addressLines[0];
387                    address2 = addressLines[1];
388                }
389            }
390            mValues.put(AddressField.ADDRESS_LINE_1, address1);
391            mValues.put(AddressField.ADDRESS_LINE_2, address2);
392        }
393    }
394}
395