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