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 static com.android.i18n.addressinput.AddressField.ADMIN_AREA;
20import static com.android.i18n.addressinput.AddressField.COUNTRY;
21import static com.android.i18n.addressinput.AddressField.DEPENDENT_LOCALITY;
22import static com.android.i18n.addressinput.AddressField.LOCALITY;
23import static com.android.i18n.addressinput.AddressField.ORGANIZATION;
24import static com.android.i18n.addressinput.AddressField.POSTAL_CODE;
25import static com.android.i18n.addressinput.AddressField.RECIPIENT;
26import static com.android.i18n.addressinput.AddressField.SORTING_CODE;
27import static com.android.i18n.addressinput.AddressField.STREET_ADDRESS;
28
29import com.android.i18n.addressinput.LookupKey.ScriptType;
30
31import java.util.Collections;
32import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35
36/**
37 * Performs various consistency checks on an AddressData. This uses a {@link FieldVerifier} to check
38 * each field in the address.
39 */
40public class StandardAddressVerifier {
41
42    protected final FieldVerifier mRootVerifier;
43
44    protected final VerifierRefiner mRefiner;
45
46    protected final Map<AddressField, List<AddressProblemType>> mProblemMap;
47
48    /**
49     * Uses the rootVerifier and {@link #DEFAULT_REFINER} to perform the standard checks on the
50     * address fields, as defined in {@link StandardChecks}.
51     */
52    public StandardAddressVerifier(FieldVerifier rootVerifier) {
53        this(rootVerifier, DEFAULT_REFINER, StandardChecks.PROBLEM_MAP);
54    }
55
56    /**
57     * Uses the rootVerifier and the refiner to perform the standard checks on the address fields,
58     * as defined in {@link StandardChecks}.
59     */
60    public StandardAddressVerifier(FieldVerifier rootVerifier, VerifierRefiner refiner) {
61        this(rootVerifier, refiner, StandardChecks.PROBLEM_MAP);
62    }
63
64    /**
65     * Uses the rootVerifier and {@link #DEFAULT_REFINER} to perform the given checks on the address
66     * fields. A reference to problemMap is maintained. It is not modified by this class, and should
67     * not be modified subsequent to this call.
68     */
69    public StandardAddressVerifier(FieldVerifier rootVerifier,
70            Map<AddressField, List<AddressProblemType>> problemMap) {
71        this(rootVerifier, DEFAULT_REFINER, problemMap);
72    }
73
74    /**
75     * Uses the rootVerifier and the refiner to perform the given checks on the address fields. A
76     * reference to problemMap is maintained. It is not modified by this class, and should not be
77     * modified subsequent to this call.
78     */
79    public StandardAddressVerifier(FieldVerifier rootVerifier, VerifierRefiner refiner,
80            Map<AddressField, List<AddressProblemType>> problemMap) {
81        mRootVerifier = rootVerifier;
82        mRefiner = refiner;
83        mProblemMap = StandardChecks.PROBLEM_MAP;
84    }
85
86    public void verify(AddressData address, AddressProblems problems) {
87        NotifyingListener listener = new NotifyingListener(this);
88        verifyAsync(address, problems, listener);
89        try {
90            listener.waitLoadingEnd();
91        } catch (InterruptedException e) {
92            throw new RuntimeException(e);
93        }
94    }
95
96    public void verifyAsync(AddressData address, AddressProblems problems,
97            DataLoadListener listener) {
98        Thread verifier = new Thread(new Verifier(address, problems, listener));
99        verifier.start();
100    }
101
102    private class Verifier implements Runnable {
103        private AddressData mAddress;
104        private AddressProblems mProblems;
105        private DataLoadListener mListener;
106
107        Verifier(AddressData address, AddressProblems problems, DataLoadListener listener) {
108            mAddress = address;
109            mProblems = problems;
110            mListener = listener;
111        }
112
113        @Override
114        public void run() {
115            mListener.dataLoadingBegin();
116
117            FieldVerifier v = mRootVerifier;
118
119            ScriptType script = null;
120            if (mAddress.getLanguageCode() != null) {
121                if (Util.isExplicitLatinScript(mAddress.getLanguageCode())) {
122                    script = ScriptType.LATIN;
123                } else {
124                    script = ScriptType.LOCAL;
125                }
126            }
127
128            // The first four calls refine the verifier, so must come first, and in this
129            // order.
130            verifyField(script, v, COUNTRY, mAddress.getPostalCountry(), mProblems);
131            if (mProblems.isEmpty()) {
132                v = v.refineVerifier(mAddress.getPostalCountry());
133                verifyField(script, v, ADMIN_AREA, mAddress.getAdministrativeArea(), mProblems);
134                if (mProblems.isEmpty()) {
135                    v = v.refineVerifier(mAddress.getAdministrativeArea());
136                    verifyField(script, v, LOCALITY, mAddress.getLocality(), mProblems);
137                    if (mProblems.isEmpty()) {
138                        v = v.refineVerifier(mAddress.getLocality());
139                        verifyField(script, v, DEPENDENT_LOCALITY,
140                                mAddress.getDependentLocality(), mProblems);
141                        if (mProblems.isEmpty()) {
142                            v = v.refineVerifier(mAddress.getDependentLocality());
143                        }
144                    }
145                }
146            }
147
148            String street = Util.joinAndSkipNulls("\n", mAddress.getAddressLine1(),
149                    mAddress.getAddressLine2());
150
151            // remaining calls don't change the field verifier
152            verifyField(script, v, POSTAL_CODE, mAddress.getPostalCode(), mProblems);
153            verifyField(script, v, STREET_ADDRESS, street, mProblems);
154            verifyField(script, v, SORTING_CODE, mAddress.getSortingCode(), mProblems);
155            verifyField(script, v, ORGANIZATION, mAddress.getOrganization(), mProblems);
156            verifyField(script, v, RECIPIENT, mAddress.getRecipient(), mProblems);
157
158            postVerify(v, mAddress, mProblems);
159
160            mListener.dataLoadingEnd();
161        }
162    }
163
164    /**
165     * Hook to perform any final processing using the final verifier.  Default does no additional
166     * verification.
167     */
168    protected void postVerify(FieldVerifier verifier, AddressData address,
169            AddressProblems problems) {
170    }
171
172    /**
173     * Hook called by verify with each verifiable field, in order.  Override to provide pre- or
174     * post-checks for all fields.
175     */
176    protected boolean verifyField(LookupKey.ScriptType script,
177            FieldVerifier verifier, AddressField field, String value,
178            AddressProblems problems) {
179        Iterator<AddressProblemType> iter = getProblemIterator(field);
180        while (iter.hasNext()) {
181            AddressProblemType prob = iter.next();
182            if (!verifyProblemField(script, verifier, prob, field, value, problems)) {
183                return false;
184            }
185        }
186        return true;
187    }
188
189    /**
190     * Hook for on-the-fly modification of the problem list.  Override to change the problems to
191     * check for a particular field.  Generally, changing the problemMap passed to the constructor
192     * is a better approach.
193     */
194    protected Iterator<AddressProblemType> getProblemIterator(AddressField field) {
195        List<AddressProblemType> list = mProblemMap.get(field);
196        if (list == null) {
197            list = Collections.emptyList();
198        }
199        return list.iterator();
200    }
201
202    /**
203     * Hook for adding special checks for particular problems and/or fields.
204     */
205    protected boolean verifyProblemField(LookupKey.ScriptType script,
206            FieldVerifier verifier, AddressProblemType problem, AddressField field,
207            String datum, AddressProblems problems) {
208        return verifier.check(script, problem, field, datum, problems);
209    }
210
211    /**
212     * This gets called with the hierarchical fields COUNTRY, ADMIN_AREA, LOCALITY,
213     * DEPENDENT_LOCALITY in order, returning the refined verifier at each step.
214     *
215     * <p>The default implementation is stateless, and delegates to the verifier to do the
216     * refinement.
217     */
218    public static class VerifierRefiner {
219
220        /**
221         * Refines the verifier.  This delegates to the verifier to perform the refinement.
222         */
223        public FieldVerifier refineVerifier(FieldVerifier v, AddressField field,
224                String subkey) {
225            return v.refineVerifier(subkey);
226        }
227
228        /**
229         * Returns a clean version of the refiner.  Since this implementation is stateless, returns
230         * this.
231         */
232        public VerifierRefiner newInstance() {
233            return this;
234        }
235    }
236
237    private static final VerifierRefiner DEFAULT_REFINER = new VerifierRefiner();
238}
239