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