001 /*
002 * Copyright 2006 Google Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package com.google.gwt.user.client.rpc;
017
018 import com.google.gwt.core.client.JavaScriptObject;
019 import com.google.gwt.core.client.GWT;
020 import com.google.gwt.user.client.Element;
021
022 import java.util.ArrayList;
023 import java.util.HashMap;
024 import java.util.Iterator;
025 import java.util.Set;
026 import java.util.Map.Entry;
027
028 /**
029 * For internal use only. Used for server call serialization.
030 * @skip
031 */
032 public class ClientSerializationStream extends SerializationStream {
033 Serializer serializer;
034 Element fContainer;
035 StringBuffer fEncodingBuffer;
036 String nextToken;
037 Element fChild;
038
039 ArrayList fSeenArray = new ArrayList();
040 int fSeenCount;
041
042 /*
043 * Map of name to index
044 */
045 HashMap fSeenStrings = new HashMap();
046
047 // For use by native methods in hosted mode.
048 //
049 // Element fSeenMap, fSeenArray;
050 HashMap fSeenMap = new HashMap();
051
052 // Array of results from an RPC call.
053 JavaScriptObject fResultsArray;
054
055 // Array of typenames used in an RPC call
056 JavaScriptObject fStringTable;
057
058 public ClientSerializationStream(Serializer serializer) {
059 this.serializer = serializer;
060 }
061
062 private static void append(StringBuffer sb, String token) {
063 // The line below expresses that a null token should never make it here.
064 // Since we treat java.lang.String just like any other object a null
065 // string should be encoded as a '~' character in the stream. Unfortunately,
066 // since this is translatable code I cannot use an assert in this code.
067 //
068 // assert(token != null);
069 //
070 sb.append(token);
071 sb.append('\uffff');
072 }
073
074 /**
075 * Appends a token to the end of the buffer.
076 */
077 public void append(String token) {
078 append(fEncodingBuffer, token);
079 }
080
081 /**
082 * Called during encode to encode a backref if the object has been seen
083 * before.
084 *
085 * @param o the object reference to be stored.
086 * @return <code>true</code> if the caller needs to encode the object (If o
087 * is non-null, never returns true twice).
088 */
089 public boolean startInstance(SerializationStreamWriter streamWriter,
090 Object instance) {
091 if (instance != null) {
092 int index = addString(getObjectTypeName(instance));
093
094 int prevIdx = privLookupAndRememberAlreadyEncodedObject(instance, System
095 .identityHashCode(instance));
096 if (prevIdx != -1) {
097 append(PREV_INSTANCE_MARKER + prevIdx);
098 return false;
099 }
100
101 append("+" + index);
102 return true;
103 }
104
105 append("~");
106 return false;
107 }
108
109 public void endInstance(SerializationStreamWriter streamWriter,
110 Object instance) {
111 // Purposely empty
112 }
113
114 private String getObjectTypeName(Object o) {
115 String typeName = GWT.getTypeName(o);
116 String serializationSignature = serializer.getSerializationSignature(typeName);
117 if (serializationSignature == null) {
118 return typeName;
119 }
120
121 return typeName + "/" + serializationSignature;
122 }
123
124 /**
125 * Returns the next token in the decoded stream. Do not call this method
126 * unless you know it will succeed, either because you have foreknowledge of
127 * the number of tokens (which is most efficient) or a call to
128 * {@link ClientTokenBuffer#canExtract} has just succeeded.
129 *
130 * @return The next token in the stream as a String, or null if a token value
131 * is absent.
132 */
133 public native String extract() /*-{
134 var resVal = this.@com.google.gwt.user.client.rpc.ClientSerializationStream::fResultsArray.pop();
135 return resVal;
136 }-*/;
137
138 /**
139 * Finds the already-decoded object associated with the given id. Note that in
140 * this case, __seen is treated as an array.
141 *
142 * @param indexToken has leading '#'
143 */
144 public Object lookupDecodedObject(String indexToken) {
145 String indexString = indexToken.substring(1);
146 int prev = Integer.parseInt(indexString);
147 return fSeenArray.get(prev);
148 }
149
150 protected native JavaScriptObject evalArray(String encoded) /*-{
151 var array = eval(encoded);
152 return array;
153 }-*/;
154
155 /**
156 * Call this method before attempting to extract any tokens. This method
157 * implementation <b>must</b> be called by any overridden version.
158 */
159 public void prepareToRead(String encoded) {
160 clearSeenObjects();
161 fResultsArray = evalArray(encoded);
162
163 // Read the stream version number
164 //
165 setVersion(Integer.parseInt(extract()));
166
167 // Read the flags from the stream
168 //
169 setFlags(Integer.parseInt(extract()));
170
171 fStringTable = getStringTableFromStream();
172 }
173
174 private native JavaScriptObject getStringTableFromStream() /*-{
175 return this.@com.google.gwt.user.client.rpc.ClientSerializationStream::fResultsArray.pop();
176 }-*/;
177
178 /**
179 * Call this method before attempting to append any tokens. This method
180 * implementation <b>must</b> be called by any overridden version.
181 */
182 public void prepareToWrite() {
183 clearSeenObjects();
184 fEncodingBuffer = new StringBuffer();
185 }
186
187 public Object prevInstance(SerializationStreamReader stream) {
188 nextToken = extract();
189 if (nextToken.startsWith(PREV_INSTANCE_MARKER)) {
190 return lookupDecodedObject(nextToken);
191 }
192
193 return null;
194 }
195
196 public String nextInstance(SerializationStreamReader stream) {
197 String current = nextToken;
198 nextToken = null;
199 if (current.equals("~")) {
200 return null;
201 }
202
203 return getInstanceName(current);
204 }
205
206 /**
207 * Called during decode to allow an object to be coalesced Note that in this
208 * case, __seen is treated as an array.
209 */
210 public void rememberDecodedObject(Object o) {
211 // Add version
212 fSeenArray.add(o);
213 }
214
215 public String toString() {
216 StringBuffer buffer = new StringBuffer();
217
218 writeHeader(buffer);
219
220 writeStringTable(buffer);
221
222 writePayload(buffer);
223
224 String packet = buffer.toString();
225
226 return packet;
227 }
228
229 private void writePayload(StringBuffer packetBuffer) {
230 packetBuffer.append(fEncodingBuffer.toString());
231 }
232
233 /**
234 * @param packetBuffer
235 */
236 private void writeHeader(StringBuffer packetBuffer) {
237 append(packetBuffer, Integer.toString(SerializationStream.SERIALIZATION_STREAM_VERSION));
238 append(packetBuffer, Integer.toString(getFlags()));
239 }
240
241 private StringBuffer writeStringTable(StringBuffer sb) {
242 int stringTableSize = fSeenStrings.size();
243 String[] stringTable = new String[stringTableSize];
244 Set entrySet = fSeenStrings.entrySet();
245 Iterator iter = entrySet.iterator();
246 while (iter.hasNext()) {
247 Entry entry = (Entry) iter.next();
248
249 stringTable[((Integer) entry.getValue()).intValue()] = (String) entry
250 .getKey();
251 }
252
253 append(sb, Integer.toString(stringTableSize));
254
255 for (int index = 0; index < stringTableSize; ++index) {
256 append(sb, stringTable[index]);
257 }
258
259 return sb;
260 }
261
262 /**
263 * Clears the list of already-seen objects. Note that __seen is used
264 * differently in the encode and decode cases. __seenCnt is only used for
265 * encode.
266 */
267 protected void clearSeenObjects() {
268 this.fSeenArray.clear();
269 this.fSeenMap.clear();
270 this.fSeenCount = 0;
271 }
272
273 protected void clearStringTable() {
274 fSeenStrings.clear();
275 fStringTable = null;
276 }
277
278 public int addString(String typeName) {
279 Object o = fSeenStrings.get(typeName);
280 int index;
281 if (o != null) {
282 index = ((Integer) o).intValue();
283 } else {
284 index = fSeenStrings.size();
285 fSeenStrings.put(typeName, new Integer(index));
286 }
287
288 return index;
289 }
290
291 public native int getStringCount() /*-{
292 return this.@com.google.gwt.user.client.rpc.ClientSerializationStream::fStringTable.length;
293 }-*/;
294
295 public native String getString(int token) /*-{
296 return this.@com.google.gwt.user.client.rpc.ClientSerializationStream::fStringTable[token];
297 }-*/;
298
299 protected void finalize() throws Throwable {
300 // Not called in web mode. This exists solely to ensure that objects
301 // allocated
302 // in native code get cleaned up when this class is gc'd.
303 //
304 clearSeenObjects();
305 }
306
307 /**
308 * Native helper for {@link #encodeObjectPrelude}. If the object has been
309 * seen before, returns its index. If not, returns -1 and remembers the object
310 * in order. Note that in this case, __seen is treated as a map.
311 *
312 * @param o the object to be remembered.
313 * @param hashCode the result of calling {@link System#identityHashCode} on o.
314 */
315 protected int privLookupAndRememberAlreadyEncodedObject(Object o, int hashCode) {
316 Integer x = (Integer) this.fSeenMap.get(new Integer(hashCode));
317 if (x != null)
318 return x.intValue();
319 this.fSeenMap.put(new Integer(hashCode), new Integer(this.fSeenCount++));
320 return -1;
321 }
322
323 public SerializationStreamObjectDecoder getObjectDecoder() {
324 return this;
325 }
326
327 public SerializationStreamObjectEncoder getObjectEncoder() {
328 return this;
329 }
330
331 protected String getInstanceName(String token) {
332 int nameIndex = Integer.parseInt(token.substring(1));
333 return getString(nameIndex);
334 }
335
336 public Object readObject() throws SerializationException {
337 Object obj = serializer.deserialize(this);
338 return obj;
339 }
340
341 public void writeObject(Object instance) throws SerializationException {
342 serializer.serialize(this, instance);
343 }
344
345 public StringTable getStringTable() {
346 return this;
347 }
348 }