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