1 /* 2 * Copyright (C) 2015 The Android Open Source Project 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 package com.android.phone.common.mail; 17 18 import java.util.HashMap; 19 import java.util.Map; 20 21 /** 22 * A utility class for creating and modifying Strings that are tagged and packed together. 23 * 24 * Uses non-printable (control chars) for internal delimiters; Intended for regular displayable 25 * strings only, so please use base64 or other encoding if you need to hide any binary data here. 26 * 27 * Binary compatible with Address.pack() format, which should migrate to use this code. 28 */ 29 public class PackedString { 30 31 /** 32 * Packing format is: 33 * element : [ value ] or [ value TAG-DELIMITER tag ] 34 * packed-string : [ element ] [ ELEMENT-DELIMITER [ element ] ]* 35 */ 36 private static final char DELIMITER_ELEMENT = '\1'; 37 private static final char DELIMITER_TAG = '\2'; 38 39 private String mString; 40 private HashMap<String, String> mExploded; 41 private static final HashMap<String, String> EMPTY_MAP = new HashMap<String, String>(); 42 43 /** 44 * Create a packed string using an already-packed string (e.g. from database) 45 * @param string packed string 46 */ PackedString(String string)47 public PackedString(String string) { 48 mString = string; 49 mExploded = null; 50 } 51 52 /** 53 * Get the value referred to by a given tag. If the tag does not exist, return null. 54 * @param tag identifier of string of interest 55 * @return returns value, or null if no string is found 56 */ get(String tag)57 public String get(String tag) { 58 if (mExploded == null) { 59 mExploded = explode(mString); 60 } 61 return mExploded.get(tag); 62 } 63 64 /** 65 * Return a map of all of the values referred to by a given tag. This is a shallow 66 * copy, don't edit the values. 67 * @return a map of the values in the packed string 68 */ unpack()69 public Map<String, String> unpack() { 70 if (mExploded == null) { 71 mExploded = explode(mString); 72 } 73 return new HashMap<String,String>(mExploded); 74 } 75 76 /** 77 * Read out all values into a map. 78 */ explode(String packed)79 private static HashMap<String, String> explode(String packed) { 80 if (packed == null || packed.length() == 0) { 81 return EMPTY_MAP; 82 } 83 HashMap<String, String> map = new HashMap<String, String>(); 84 85 int length = packed.length(); 86 int elementStartIndex = 0; 87 int elementEndIndex = 0; 88 int tagEndIndex = packed.indexOf(DELIMITER_TAG); 89 90 while (elementStartIndex < length) { 91 elementEndIndex = packed.indexOf(DELIMITER_ELEMENT, elementStartIndex); 92 if (elementEndIndex == -1) { 93 elementEndIndex = length; 94 } 95 String tag; 96 String value; 97 if (tagEndIndex == -1 || elementEndIndex <= tagEndIndex) { 98 // in this case the DELIMITER_PERSONAL is in a future pair (or not found) 99 // so synthesize a positional tag for the value, and don't update tagEndIndex 100 value = packed.substring(elementStartIndex, elementEndIndex); 101 tag = Integer.toString(map.size()); 102 } else { 103 value = packed.substring(elementStartIndex, tagEndIndex); 104 tag = packed.substring(tagEndIndex + 1, elementEndIndex); 105 // scan forward for next tag, if any 106 tagEndIndex = packed.indexOf(DELIMITER_TAG, elementEndIndex + 1); 107 } 108 map.put(tag, value); 109 elementStartIndex = elementEndIndex + 1; 110 } 111 112 return map; 113 } 114 115 /** 116 * Builder class for creating PackedString values. Can also be used for editing existing 117 * PackedString representations. 118 */ 119 static public class Builder { 120 HashMap<String, String> mMap; 121 122 /** 123 * Create a builder that's empty (for filling) 124 */ Builder()125 public Builder() { 126 mMap = new HashMap<String, String>(); 127 } 128 129 /** 130 * Create a builder using the values of an existing PackedString (for editing). 131 */ Builder(String packed)132 public Builder(String packed) { 133 mMap = explode(packed); 134 } 135 136 /** 137 * Add a tagged value 138 * @param tag identifier of string of interest 139 * @param value the value to record in this position. null to delete entry. 140 */ put(String tag, String value)141 public void put(String tag, String value) { 142 if (value == null) { 143 mMap.remove(tag); 144 } else { 145 mMap.put(tag, value); 146 } 147 } 148 149 /** 150 * Get the value referred to by a given tag. If the tag does not exist, return null. 151 * @param tag identifier of string of interest 152 * @return returns value, or null if no string is found 153 */ get(String tag)154 public String get(String tag) { 155 return mMap.get(tag); 156 } 157 158 /** 159 * Pack the values and return a single, encoded string 160 */ 161 @Override toString()162 public String toString() { 163 StringBuilder sb = new StringBuilder(); 164 for (Map.Entry<String,String> entry : mMap.entrySet()) { 165 if (sb.length() > 0) { 166 sb.append(DELIMITER_ELEMENT); 167 } 168 sb.append(entry.getValue()); 169 sb.append(DELIMITER_TAG); 170 sb.append(entry.getKey()); 171 } 172 return sb.toString(); 173 } 174 } 175 } 176