blob: 87702161070b5422df8e2348764f5ffacb8afd24 [file] [log] [blame]
Siva Velusamy0d857b92015-04-22 10:23:56 -07001package android.view;
2
3import android.annotation.NonNull;
4import android.annotation.Nullable;
5
6import java.io.ByteArrayOutputStream;
7import java.io.DataOutputStream;
8import java.io.IOException;
9import java.nio.charset.Charset;
10import java.util.HashMap;
11import java.util.Map;
12
13/**
14 * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
15 * view hierarchies (the view tree, along with the properties for each view) to a stream.
16 *
17 * It is typically used as follows:
18 * <pre>
19 * ViewHierarchyEncoder e = new ViewHierarchyEncoder();
20 *
21 * for (View view : views) {
22 * e.beginObject(view);
23 * e.addProperty("prop1", value);
24 * ...
25 * e.endObject();
26 * }
27 *
28 * // repeat above snippet for each view, finally end with:
29 * e.endStream();
30 * </pre>
31 *
32 * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
33 * corresponding to each view) with the property name as the key and the property value
34 * as the value.
35 *
36 * <p>Since the property names are practically the same across all views, rather than using
37 * the property name directly as the key, we use a short integer id corresponding to each
38 * property name as the key. A final map is added at the end which contains the mapping
39 * from the integer to its property name.
40 *
41 * <p>A value is encoded as a single byte type identifier followed by the encoding of the
42 * value. Only primitive types are supported as values, in addition to the Map type.
43 *
44 * @hide
45 */
46public class ViewHierarchyEncoder {
47 // Prefixes for simple primitives. These match the JNI definitions.
48 private static final byte SIG_BOOLEAN = 'Z';
49 private static final byte SIG_BYTE = 'B';
50 private static final byte SIG_SHORT = 'S';
51 private static final byte SIG_INT = 'I';
52 private static final byte SIG_LONG = 'J';
53 private static final byte SIG_FLOAT = 'F';
54 private static final byte SIG_DOUBLE = 'D';
55
56 // Prefixes for some commonly used objects
57 private static final byte SIG_STRING = 'R';
58
59 private static final byte SIG_MAP = 'M'; // a map with an short key
60 private static final short SIG_END_MAP = 0;
61
62 private final DataOutputStream mStream;
63
64 private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
65 private short mPropertyId = 1;
66 private Charset mCharset = Charset.forName("utf-8");
67
68 public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
69 mStream = new DataOutputStream(stream);
70 }
71
72 public void beginObject(@NonNull Object o) {
73 startPropertyMap();
74 addProperty("meta:__name__", o.getClass().getName());
75 addProperty("meta:__hash__", o.hashCode());
76 }
77
78 public void endObject() {
79 endPropertyMap();
80 }
81
82 public void endStream() {
83 // write out the string table
84 startPropertyMap();
85 addProperty("__name__", "propertyIndex");
86 for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
87 writeShort(entry.getValue());
88 writeString(entry.getKey());
89 }
90 endPropertyMap();
91 }
92
93 public void addProperty(@NonNull String name, boolean v) {
94 writeShort(createPropertyIndex(name));
95 writeBoolean(v);
96 }
97
98 public void addProperty(@NonNull String name, short s) {
99 writeShort(createPropertyIndex(name));
100 writeShort(s);
101 }
102
103 public void addProperty(@NonNull String name, int v) {
104 writeShort(createPropertyIndex(name));
105 writeInt(v);
106 }
107
108 public void addProperty(@NonNull String name, float v) {
109 writeShort(createPropertyIndex(name));
110 writeFloat(v);
111 }
112
113 public void addProperty(@NonNull String name, @Nullable String s) {
114 writeShort(createPropertyIndex(name));
115 writeString(s);
116 }
117
118 /**
119 * Writes the given name as the property name, and leaves it to the callee
120 * to fill in value for this property.
121 */
122 public void addPropertyKey(@NonNull String name) {
123 writeShort(createPropertyIndex(name));
124 }
125
126 private short createPropertyIndex(@NonNull String name) {
127 Short index = mPropertyNames.get(name);
128 if (index == null) {
129 index = mPropertyId++;
130 mPropertyNames.put(name, index);
131 }
132
133 return index;
134 }
135
136 private void startPropertyMap() {
137 try {
138 mStream.write(SIG_MAP);
139 } catch (IOException e) {
140 // does not happen since the stream simply wraps a ByteArrayOutputStream
141 }
142 }
143
144 private void endPropertyMap() {
145 writeShort(SIG_END_MAP);
146 }
147
148 private void writeBoolean(boolean v) {
149 try {
150 mStream.write(SIG_BOOLEAN);
151 mStream.write(v ? 1 : 0);
152 } catch (IOException e) {
153 // does not happen since the stream simply wraps a ByteArrayOutputStream
154 }
155 }
156
157 private void writeShort(short s) {
158 try {
159 mStream.write(SIG_SHORT);
160 mStream.writeShort(s);
161 } catch (IOException e) {
162 // does not happen since the stream simply wraps a ByteArrayOutputStream
163 }
164 }
165
166 private void writeInt(int i) {
167 try {
168 mStream.write(SIG_INT);
169 mStream.writeInt(i);
170 } catch (IOException e) {
171 // does not happen since the stream simply wraps a ByteArrayOutputStream
172 }
173 }
174
175 private void writeFloat(float v) {
176 try {
177 mStream.write(SIG_FLOAT);
178 mStream.writeFloat(v);
179 } catch (IOException e) {
180 // does not happen since the stream simply wraps a ByteArrayOutputStream
181 }
182 }
183
184 private void writeString(@Nullable String s) {
185 if (s == null) {
186 s = "";
187 }
188
189 try {
190 mStream.write(SIG_STRING);
191 byte[] bytes = s.getBytes(mCharset);
192
193 short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
194 mStream.writeShort(len);
195
196 mStream.write(bytes, 0, len);
197 } catch (IOException e) {
198 // does not happen since the stream simply wraps a ByteArrayOutputStream
199 }
200 }
201}