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    }