1345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/*
2345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Copyright (C) 2010 The Android Open Source Project
3345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
4345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
5345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * you may not use this file except in compliance with the License.
6345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * You may obtain a copy of the License at
7345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
8345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
9345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
10345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software
11345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
12345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * See the License for the specific language governing permissions and
14345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * limitations under the License.
15345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */
16345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
17345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpackage com.android.emailcommon.mail;
18345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
19345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.HashMap;
20345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.Map;
21345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
22345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/**
23345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * A utility class for creating and modifying Strings that are tagged and packed together.
24345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
25345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Uses non-printable (control chars) for internal delimiters;  Intended for regular displayable
26345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * strings only, so please use base64 or other encoding if you need to hide any binary data here.
27345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
28345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Binary compatible with Address.pack() format, which should migrate to use this code.
29345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */
30345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpublic class PackedString {
31345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
32345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
33345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Packing format is:
34345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *   element : [ value ] or [ value TAG-DELIMITER tag ]
35345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *   packed-string : [ element ] [ ELEMENT-DELIMITER [ element ] ]*
36345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
37345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final char DELIMITER_ELEMENT = '\1';
38345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final char DELIMITER_TAG = '\2';
39345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
40345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private String mString;
41345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private HashMap<String, String> mExploded;
42345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final HashMap<String, String> EMPTY_MAP = new HashMap<String, String>();
43345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
44345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
45345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Create a packed string using an already-packed string (e.g. from database)
46345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param string packed string
47345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
48345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public PackedString(String string) {
49345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        mString = string;
50345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        mExploded = null;
51345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
52345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
53345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
54345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get the value referred to by a given tag.  If the tag does not exist, return null.
55345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param tag identifier of string of interest
56345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return returns value, or null if no string is found
57345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
58345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String get(String tag) {
59345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mExploded == null) {
60345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            mExploded = explode(mString);
61345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
62345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return mExploded.get(tag);
63345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
64345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
65345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
66345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Return a map of all of the values referred to by a given tag.  This is a shallow
67345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * copy, don't edit the values.
68345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return a map of the values in the packed string
69345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
70345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public Map<String, String> unpack() {
71345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mExploded == null) {
72345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            mExploded = explode(mString);
73345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
74345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return new HashMap<String,String>(mExploded);
75345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
76345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
77345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
78345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Read out all values into a map.
79345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
80345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static HashMap<String, String> explode(String packed) {
81345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (packed == null || packed.length() == 0) {
82345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EMPTY_MAP;
83345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
84345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        HashMap<String, String> map = new HashMap<String, String>();
85345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
86345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int length = packed.length();
87345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int elementStartIndex = 0;
88345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int elementEndIndex = 0;
89345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int tagEndIndex = packed.indexOf(DELIMITER_TAG);
90345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
91345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        while (elementStartIndex < length) {
92345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            elementEndIndex = packed.indexOf(DELIMITER_ELEMENT, elementStartIndex);
93345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (elementEndIndex == -1) {
94345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                elementEndIndex = length;
95345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
96345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            String tag;
97345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            String value;
98345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (tagEndIndex == -1 || elementEndIndex <= tagEndIndex) {
99345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // in this case the DELIMITER_PERSONAL is in a future pair (or not found)
100345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // so synthesize a positional tag for the value, and don't update tagEndIndex
101345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                value = packed.substring(elementStartIndex, elementEndIndex);
102345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                tag = Integer.toString(map.size());
103345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            } else {
104345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                value = packed.substring(elementStartIndex, tagEndIndex);
105345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                tag = packed.substring(tagEndIndex + 1, elementEndIndex);
106345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // scan forward for next tag, if any
107345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                tagEndIndex = packed.indexOf(DELIMITER_TAG, elementEndIndex + 1);
108345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
109345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            map.put(tag, value);
110345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            elementStartIndex = elementEndIndex + 1;
111345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
112345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
113345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return map;
114345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
115345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
116345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
117345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Builder class for creating PackedString values.  Can also be used for editing existing
118345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * PackedString representations.
119345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
120345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    static public class Builder {
121345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        HashMap<String, String> mMap;
122345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
123345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /**
124345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * Create a builder that's empty (for filling)
125345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         */
126345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        public Builder() {
127345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            mMap = new HashMap<String, String>();
128345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
129345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
130345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /**
131345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * Create a builder using the values of an existing PackedString (for editing).
132345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         */
133345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        public Builder(String packed) {
134345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            mMap = explode(packed);
135345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
136345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
137345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /**
138345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * Add a tagged value
139345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * @param tag identifier of string of interest
140345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * @param value the value to record in this position.  null to delete entry.
141345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         */
142345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        public void put(String tag, String value) {
143345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (value == null) {
144345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                mMap.remove(tag);
145345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            } else {
146345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                mMap.put(tag, value);
147345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
148345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
149345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
150345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /**
151345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * Get the value referred to by a given tag.  If the tag does not exist, return null.
152345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * @param tag identifier of string of interest
153345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * @return returns value, or null if no string is found
154345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         */
155345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        public String get(String tag) {
156345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mMap.get(tag);
157345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
158345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
159345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /**
160345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         * Pack the values and return a single, encoded string
161345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein         */
162345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        @Override
163345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        public String toString() {
164345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            StringBuilder sb = new StringBuilder();
165345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            for (Map.Entry<String,String> entry : mMap.entrySet()) {
166345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                if (sb.length() > 0) {
167345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    sb.append(DELIMITER_ELEMENT);
168345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                }
169345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                sb.append(entry.getValue());
170345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                sb.append(DELIMITER_TAG);
171345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                sb.append(entry.getKey());
172345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
173345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return sb.toString();
174345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
175345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
176345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein}
177