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 
17 package com.android.emailcommon.mail;
18 
19 import java.util.HashMap;
20 import 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  */
30 public 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      */
PackedString(String string)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      */
get(String tag)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      */
unpack()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      */
explode(String packed)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          */
Builder()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          */
Builder(String packed)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          */
put(String tag, String value)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          */
get(String tag)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
toString()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