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.Collections;
20import java.util.EnumMap;
21import java.util.HashSet;
22import java.util.Map;
23import java.util.Set;
24import java.util.regex.Matcher;
25import java.util.regex.Pattern;
26
27/**
28 * Wraps a Map of address property data to provide the AddressVerificationData API.
29 */
30class AddressVerificationData implements DataSource {
31
32    private final Map<String, String> mPropertiesMap;
33
34    private static final Pattern KEY_VALUES_PATTERN = Pattern.compile("\"([^\"]+)\":\"([^\"]*)\"");
35
36    private static final Pattern SEPARATOR_PATTERN = Pattern.compile("\",\"");
37
38    /**
39     * Constructs from a map of address property data.  This keeps a reference to the map.  This
40     * does not mutate the map. The map should not be mutated subsequent to this call.
41     */
42    AddressVerificationData(Map<String, String> propertiesMap) {
43        mPropertiesMap = propertiesMap;
44    }
45
46    @Override
47    public AddressVerificationNodeData get(String key) {
48        String json = mPropertiesMap.get(key);
49        if (json != null && isValidKey(key)) {
50            return createNodeData(json);
51        }
52        return null;
53    }
54
55    /**
56     * Returns a set of the keys for which verification data is provided.  The returned set is
57     * immutable.
58     */
59    Set<String> keys() {
60        Set<String> result = new HashSet<String>();
61        for (String key : mPropertiesMap.keySet()) {
62            if (isValidKey(key)) {
63                result.add(key);
64            }
65        }
66        return Collections.unmodifiableSet(result);
67    }
68
69    /**
70     * We can be initialized with the full set of address information, but validation only uses
71     * info prefixed with "data" (in particular, no info prefixed with "examples").
72     */
73    private boolean isValidKey(String key) {
74        return key.startsWith("data");
75    }
76
77    /**
78     * Returns the contents of the JSON-format string as a map.
79     */
80    AddressVerificationNodeData createNodeData(String json) {
81        // Remove leading and trailing { and }.
82        json = json.substring(1, json.length() - 1);
83        Map<AddressDataKey, String> map =
84                new EnumMap<AddressDataKey, String>(AddressDataKey.class);
85
86        // our objects are very simple so we parse manually
87        // - no double quotes within strings
88        // - no extra spaces
89        // can't use split "," since some data has commas in it.
90        Matcher sm = SEPARATOR_PATTERN.matcher(json);
91        int pos = 0;
92        while (pos < json.length()) {
93            String pair;
94            if (sm.find()) {
95                pair = json.substring(pos, sm.start() + 1);
96                pos = sm.start() + 2;
97            } else {
98                pair = json.substring(pos);
99                pos = json.length();
100            }
101
102            Matcher m = KEY_VALUES_PATTERN.matcher(pair);
103            if (m.matches()) {
104                String value = m.group(2);
105
106                // Remove escaped backslashes.
107                // Java regex doesn't handle a replacement String consisting of
108                // a single backslash, and treats a replacement String consisting of
109                // two backslashes as two backslashes instead of one.  So there's
110                // no way to use regex to replace a match with a single backslash,
111                // apparently.
112                if (value.length() > 0) {
113                    char[] linechars = m.group(2).toCharArray();
114                    int w = 1;
115                    for (int r = w; r < linechars.length; ++r) {
116                        char c = linechars[r];
117                        if (c == '\\' && linechars[w - 1] == '\\') {
118                            // don't increment w;
119                            continue;
120                        }
121                        linechars[w++] = c;
122                    }
123                    value = new String(linechars, 0, w);
124                }
125
126                AddressDataKey df = AddressDataKey.get(m.group(1));
127                if (df == null) {
128                    // Skip this data - it isn't used in the Android version.
129                } else {
130                    map.put(df, value);
131                }
132            } else {
133                // This is a runtime data sanity check.  The data should be
134                // checked when the data is built.  The JSON data string should
135                // be parsable into string pairs using SEP_PAT.
136                throw new RuntimeException("could not match '" + pair + "' in '" + json + "'");
137            }
138        }
139
140        return new AddressVerificationNodeData(map);
141    }
142
143    @Override
144    public AddressVerificationNodeData getDefaultData(String key) {
145        // gets country key
146        if (key.split("/").length > 1) {
147            String[] parts = key.split("/");
148            key = parts[0] + "/" + parts[1];
149        }
150
151        AddressVerificationNodeData data = get(key);
152        if (data == null) {
153            throw new RuntimeException("failed to get default data with key " + key);
154        }
155        return data;
156    }
157}
158