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.voicemail.impl.mail; 17 18 import android.util.ArrayMap; 19 import java.util.Map; 20 21 /** 22 * A utility class for creating and modifying Strings that are tagged and packed together. 23 * 24 * <p>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 * <p>Binary compatible with Address.pack() format, which should migrate to use this code. 28 */ 29 public class PackedString { 30 31 /** 32 * Packing format is: element : [ value ] or [ value TAG-DELIMITER tag ] packed-string : [ element 33 * ] [ ELEMENT-DELIMITER [ element ] ]* 34 */ 35 private static final char DELIMITER_ELEMENT = '\1'; 36 37 private static final char DELIMITER_TAG = '\2'; 38 39 private String string; 40 private ArrayMap<String, String> exploded; 41 private static final ArrayMap<String, String> EMPTY_MAP = new ArrayMap<String, String>(); 42 43 /** 44 * Create a packed string using an already-packed string (e.g. from database) 45 * 46 * @param string packed string 47 */ PackedString(String string)48 public PackedString(String string) { 49 this.string = string; 50 exploded = null; 51 } 52 53 /** 54 * Get the value referred to by a given tag. If the tag does not exist, return null. 55 * 56 * @param tag identifier of string of interest 57 * @return returns value, or null if no string is found 58 */ get(String tag)59 public String get(String tag) { 60 if (exploded == null) { 61 exploded = explode(string); 62 } 63 return exploded.get(tag); 64 } 65 66 /** 67 * Return a map of all of the values referred to by a given tag. This is a shallow copy, don't 68 * edit the values. 69 * 70 * @return a map of the values in the packed string 71 */ unpack()72 public Map<String, String> unpack() { 73 if (exploded == null) { 74 exploded = explode(string); 75 } 76 return new ArrayMap<String, String>(exploded); 77 } 78 79 /** Read out all values into a map. */ explode(String packed)80 private static ArrayMap<String, String> explode(String packed) { 81 if (packed == null || packed.length() == 0) { 82 return EMPTY_MAP; 83 } 84 ArrayMap<String, String> map = new ArrayMap<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 public static class Builder { 121 ArrayMap<String, String> map; 122 123 /** Create a builder that's empty (for filling) */ Builder()124 public Builder() { 125 map = new ArrayMap<String, String>(); 126 } 127 128 /** Create a builder using the values of an existing PackedString (for editing). */ Builder(String packed)129 public Builder(String packed) { 130 map = explode(packed); 131 } 132 133 /** 134 * Add a tagged value 135 * 136 * @param tag identifier of string of interest 137 * @param value the value to record in this position. null to delete entry. 138 */ put(String tag, String value)139 public void put(String tag, String value) { 140 if (value == null) { 141 map.remove(tag); 142 } else { 143 map.put(tag, value); 144 } 145 } 146 147 /** 148 * Get the value referred to by a given tag. If the tag does not exist, return null. 149 * 150 * @param tag identifier of string of interest 151 * @return returns value, or null if no string is found 152 */ get(String tag)153 public String get(String tag) { 154 return map.get(tag); 155 } 156 157 /** Pack the values and return a single, encoded string */ 158 @Override toString()159 public String toString() { 160 StringBuilder sb = new StringBuilder(); 161 for (Map.Entry<String, String> entry : map.entrySet()) { 162 if (sb.length() > 0) { 163 sb.append(DELIMITER_ELEMENT); 164 } 165 sb.append(entry.getValue()); 166 sb.append(DELIMITER_TAG); 167 sb.append(entry.getKey()); 168 } 169 return sb.toString(); 170 } 171 } 172 } 173