KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > user > server > rpc > impl > ServerSerializationStreamWriter


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.user.server.rpc.impl;
17
18 import com.google.gwt.user.client.rpc.SerializationException;
19 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
20 import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter;
21
22 import java.lang.reflect.Field JavaDoc;
23 import java.lang.reflect.InvocationTargetException JavaDoc;
24 import java.lang.reflect.Method JavaDoc;
25 import java.lang.reflect.Modifier JavaDoc;
26 import java.util.ArrayList JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.IdentityHashMap JavaDoc;
29
30 /**
31  * For internal use only. Used for server call serialization. This class is
32  * carefully matched with the client-side version.
33  */

34 public final class ServerSerializationStreamWriter extends
35     AbstractSerializationStreamWriter {
36
37   private static final char NON_BREAKING_HYPHEN = '\u2011';
38
39   /**
40    * Number of escaped JS Chars.
41    */

42   private static final int NUMBER_OF_JS_ESCAPED_CHARS = 128;
43
44   /**
45    * A list of any characters that need escaping when printing a JavaScript
46    * string literal. Contains a 0 if the character does not need escaping,
47    * otherwise contains the character to escape with.
48    */

49   private static final char[] JS_CHARS_ESCAPED = new char[NUMBER_OF_JS_ESCAPED_CHARS];
50
51   /**
52    * This defines the character used by JavaScript to mark the start of an
53    * escape sequence.
54    */

55   private static final char JS_ESCAPE_CHAR = '\\';
56
57   /**
58    * This defines the character used to enclose JavaScript strings.
59    */

60   private static final char JS_QUOTE_CHAR = '\"';
61
62   /**
63    * Index into this array using a nibble, 4 bits, to get the corresponding
64    * hexa-decimal character representation.
65    */

66   private static final char NIBBLE_TO_HEX_CHAR[] = {
67       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
68       'E', 'F'};
69
70   static {
71     /*
72      * NOTE: The JS VM in IE6 & IE7 do not interpret \v correctly. They convert
73      * JavaScript Vertical Tab character '\v' into 'v'. As such, we do not use
74      * the short form of the unicode escape here.
75      */

76     JS_CHARS_ESCAPED['\u0000'] = '0';
77     JS_CHARS_ESCAPED['\b'] = 'b';
78     JS_CHARS_ESCAPED['\t'] = 't';
79     JS_CHARS_ESCAPED['\n'] = 'n';
80     JS_CHARS_ESCAPED['\f'] = 'f';
81     JS_CHARS_ESCAPED['\r'] = 'r';
82     JS_CHARS_ESCAPED[JS_ESCAPE_CHAR] = JS_ESCAPE_CHAR;
83     JS_CHARS_ESCAPED[JS_QUOTE_CHAR] = JS_QUOTE_CHAR;
84   }
85
86   /**
87    * This method takes a string and outputs a JavaScript string literal. The
88    * data is surrounded with quotes, and any contained characters that need to
89    * be escaped are mapped onto their escape sequence.
90    *
91    * Assumptions: We are targeting a version of JavaScript that that is later
92    * than 1.3 that supports unicode strings.
93    */

94   private static String JavaDoc escapeString(String JavaDoc toEscape) {
95     // make output big enough to escape every character (plus the quotes)
96
char[] input = toEscape.toCharArray();
97     CharVector charVector = new CharVector(input.length * 2 + 2, input.length);
98
99     charVector.add(JS_QUOTE_CHAR);
100
101     for (int i = 0, n = input.length; i < n; ++i) {
102       char c = input[i];
103       if (c < NUMBER_OF_JS_ESCAPED_CHARS && JS_CHARS_ESCAPED[c] != 0) {
104         charVector.add(JS_ESCAPE_CHAR);
105         charVector.add(JS_CHARS_ESCAPED[c]);
106       } else if (needsUnicodeEscape(c)) {
107         charVector.add(JS_ESCAPE_CHAR);
108         unicodeEscape(c, charVector);
109       } else {
110         charVector.add(c);
111       }
112     }
113
114     charVector.add(JS_QUOTE_CHAR);
115     return String.valueOf(charVector.asArray(), 0, charVector.getSize());
116   }
117
118   /**
119    * Returns <code>true</code> if the character requires the \\uXXXX unicode
120    * character escape sequence. This is necessary if the raw character could be
121    * consumed and/or interpreted as a special character when the JSON encoded
122    * response is evaluated. For example, 0x2028 and 0x2029 are alternate line
123    * endings for JS per ECMA-232, which are respected by Firefox and
124    * Mozilla.
125    *
126    * @param ch character to check
127    * @return <code>true</code> if the character requires the \\uXXXX unicode
128    * character escape
129    *
130    * Notes:
131    * <ol>
132    * <li> The following cases are a more conservative set of cases which are are
133    * in the future proofing space as opposed to the required minimal set. We
134    * could remove these and still pass our tests.
135    * <ul>
136    * <li>UNASSIGNED - 6359</li>
137    * <li>NON_SPACING_MARK - 530</li>
138    * <li>ENCLOSING_MARK - 10</li>
139    * <li>COMBINING_SPACE_MARK - 131</li>
140    * <li>SPACE_SEPARATOR - 19</li>
141    * <li>CONTROL - 65</li>
142    * <li>PRIVATE_USE - 6400</li>
143    * <li>DASH_PUNCTUATION - 1</li>
144    * <li>Total Characters Escaped: 13515</li>
145    * </ul>
146    * </li>
147    * <li> The following cases are the minimal amount of escaping required to
148    * prevent test failure.
149    * <ul>
150    * <li>LINE_SEPARATOR - 1</li>
151    * <li>PARAGRAPH_SEPARATOR - 1</li>
152    * <li>FORMAT - 32</li>
153    * <li>SURROGATE - 2048</li>
154    * <li>Total Characters Escaped: 2082</li>
155    * </li>
156    * </ul>
157    * </li>
158    * </ol>
159    */

160   private static boolean needsUnicodeEscape(char ch) {
161     switch (Character.getType(ch)) {
162       // Conservative
163
case Character.COMBINING_SPACING_MARK:
164       case Character.ENCLOSING_MARK:
165       case Character.NON_SPACING_MARK:
166       case Character.UNASSIGNED:
167       case Character.PRIVATE_USE:
168       case Character.SPACE_SEPARATOR:
169       case Character.CONTROL:
170
171       // Minimal
172
case Character.LINE_SEPARATOR:
173       case Character.FORMAT:
174       case Character.PARAGRAPH_SEPARATOR:
175       case Character.SURROGATE:
176         return true;
177
178       default:
179         if (ch == NON_BREAKING_HYPHEN) {
180           // This can be expanded into a break followed by a hyphen
181
return true;
182         }
183         break;
184     }
185
186     return false;
187   }
188
189   /**
190    * Writes either the two or four character escape sequence for a character.
191    *
192    *
193    * @param ch character to unicode escape
194    * @param charVector char vector to receive the unicode escaped representation
195    */

196   private static void unicodeEscape(char ch, CharVector charVector) {
197     if (ch < 256) {
198       charVector.add('x');
199       charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 4) & 0x0F]);
200       charVector.add(NIBBLE_TO_HEX_CHAR[ch & 0x0F]);
201     } else {
202       charVector.add('u');
203       charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 12) & 0x0F]);
204       charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 8) & 0x0F]);
205       charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 4) & 0x0F]);
206       charVector.add(NIBBLE_TO_HEX_CHAR[ch & 0x0F]);
207     }
208   }
209
210   private int objectCount;
211
212   private IdentityHashMap JavaDoc objectMap = new IdentityHashMap JavaDoc();
213
214   private ServerSerializableTypeOracle serializableTypeOracle;
215
216   private HashMap JavaDoc stringMap = new HashMap JavaDoc();
217
218   private ArrayList JavaDoc stringTable = new ArrayList JavaDoc();
219
220   private ArrayList JavaDoc tokenList = new ArrayList JavaDoc();
221
222   private int tokenListCharCount;
223
224   public ServerSerializationStreamWriter(
225       ServerSerializableTypeOracle serializableTypeOracle) {
226     this.serializableTypeOracle = serializableTypeOracle;
227   }
228
229   public void prepareToWrite() {
230     objectCount = 0;
231     objectMap.clear();
232     tokenList.clear();
233     tokenListCharCount = 0;
234     stringMap.clear();
235     stringTable.clear();
236   }
237
238   public void serializeValue(Object JavaDoc value, Class JavaDoc type)
239       throws SerializationException {
240     if (type == boolean.class) {
241       writeBoolean(((Boolean JavaDoc) value).booleanValue());
242     } else if (type == byte.class) {
243       writeByte(((Byte JavaDoc) value).byteValue());
244     } else if (type == char.class) {
245       writeChar(((Character JavaDoc) value).charValue());
246     } else if (type == double.class) {
247       writeDouble(((Double JavaDoc) value).doubleValue());
248     } else if (type == float.class) {
249       writeFloat(((Float JavaDoc) value).floatValue());
250     } else if (type == int.class) {
251       writeInt(((Integer JavaDoc) value).intValue());
252     } else if (type == long.class) {
253       writeLong(((Long JavaDoc) value).longValue());
254     } else if (type == short.class) {
255       writeShort(((Short JavaDoc) value).shortValue());
256     } else if (type == String JavaDoc.class) {
257       writeString((String JavaDoc) value);
258     } else {
259       writeObject(value);
260     }
261   }
262
263   /**
264    * Build an array of JavaScript string literals that can be decoded by the
265    * client via the eval function.
266    *
267    * NOTE: We build the array in reverse so the client can simply use the pop
268    * function to remove the next item from the list.
269    */

270   public String JavaDoc toString() {
271     // Build a JavaScript string (with escaping, of course).
272
// We take a guess at how big to make to buffer to avoid numerous resizes.
273
//
274
int capacityGuess = 2 * tokenListCharCount + 2 * tokenList.size();
275     StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(capacityGuess);
276     buffer.append("[");
277     writePayload(buffer);
278     writeStringTable(buffer);
279     writeHeader(buffer);
280     buffer.append("]");
281     return buffer.toString();
282   }
283
284   protected int addString(String JavaDoc string) {
285     if (string == null) {
286       return 0;
287     }
288     Integer JavaDoc o = (Integer JavaDoc) stringMap.get(string);
289     if (o != null) {
290       return o.intValue();
291     }
292     stringTable.add(string);
293     // index is 1-based
294
int index = stringTable.size();
295     stringMap.put(string, new Integer JavaDoc(index));
296     return index;
297   }
298
299   protected void append(String JavaDoc token) {
300     tokenList.add(token);
301     if (token != null) {
302       tokenListCharCount += token.length();
303     }
304   }
305
306   protected int getIndexForObject(Object JavaDoc instance) {
307     Integer JavaDoc o = (Integer JavaDoc) objectMap.get(instance);
308     if (o != null) {
309       return o.intValue();
310     }
311     return -1;
312   }
313
314   protected String JavaDoc getObjectTypeSignature(Object JavaDoc instance) {
315     if (shouldEnforceTypeVersioning()) {
316       return serializableTypeOracle.encodeSerializedInstanceReference(instance.getClass());
317     } else {
318       return serializableTypeOracle.getSerializedTypeName(instance.getClass());
319     }
320   }
321
322   protected void saveIndexForObject(Object JavaDoc instance) {
323     objectMap.put(instance, new Integer JavaDoc(objectCount++));
324   }
325
326   protected void serialize(Object JavaDoc instance, String JavaDoc typeSignature)
327       throws SerializationException {
328     serializeImpl(instance, instance.getClass());
329   }
330
331   private void serializeClass(Object JavaDoc instance, Class JavaDoc instanceClass)
332       throws SerializationException {
333     assert (instance != null);
334
335     Field JavaDoc[] declFields = instanceClass.getDeclaredFields();
336     Field JavaDoc[] serializableFields = serializableTypeOracle.applyFieldSerializationPolicy(declFields);
337     for (int index = 0; index < serializableFields.length; ++index) {
338       Field JavaDoc declField = serializableFields[index];
339       assert (declField != null);
340
341       boolean isAccessible = declField.isAccessible();
342       boolean needsAccessOverride = !isAccessible
343           && !Modifier.isPublic(declField.getModifiers());
344       if (needsAccessOverride) {
345         // Override the access restrictions
346
declField.setAccessible(true);
347       }
348
349       Object JavaDoc value;
350       try {
351         value = declField.get(instance);
352         serializeValue(value, declField.getType());
353
354       } catch (IllegalArgumentException JavaDoc e) {
355         throw new SerializationException(e);
356
357       } catch (IllegalAccessException JavaDoc e) {
358         throw new SerializationException(e);
359       }
360
361       if (needsAccessOverride) {
362         // Restore the access restrictions
363
declField.setAccessible(isAccessible);
364       }
365     }
366
367     Class JavaDoc superClass = instanceClass.getSuperclass();
368     if (superClass != null && serializableTypeOracle.isSerializable(superClass)) {
369       serializeImpl(instance, superClass);
370     }
371   }
372
373   private void serializeImpl(Object JavaDoc instance, Class JavaDoc instanceClass)
374       throws SerializationException {
375
376     assert (instance != null);
377
378     Class JavaDoc customSerializer = serializableTypeOracle.hasCustomFieldSerializer(instanceClass);
379     if (customSerializer != null) {
380       serializeWithCustomSerializer(customSerializer, instance, instanceClass);
381     } else {
382       // Arrays are serialized using custom serializers so we should never get
383
// here for array types.
384
//
385
assert (!instanceClass.isArray());
386       serializeClass(instance, instanceClass);
387     }
388   }
389
390   private void serializeWithCustomSerializer(Class JavaDoc customSerializer,
391       Object JavaDoc instance, Class JavaDoc instanceClass) throws SerializationException {
392
393     Method JavaDoc serialize;
394     try {
395       if (instanceClass.isArray()) {
396         Class JavaDoc componentType = instanceClass.getComponentType();
397         if (!componentType.isPrimitive()) {
398           instanceClass = Class.forName("[Ljava.lang.Object;");
399         }
400       }
401
402       serialize = customSerializer.getMethod("serialize", new Class JavaDoc[] {
403           SerializationStreamWriter.class, instanceClass});
404
405       serialize.invoke(null, new Object JavaDoc[] {this, instance});
406
407     } catch (SecurityException JavaDoc e) {
408       throw new SerializationException(e);
409
410     } catch (NoSuchMethodException JavaDoc e) {
411       throw new SerializationException(e);
412
413     } catch (IllegalArgumentException JavaDoc e) {
414       throw new SerializationException(e);
415
416     } catch (IllegalAccessException JavaDoc e) {
417       throw new SerializationException(e);
418
419     } catch (InvocationTargetException JavaDoc e) {
420       throw new SerializationException(e);
421
422     } catch (ClassNotFoundException JavaDoc e) {
423       throw new SerializationException(e);
424     }
425   }
426
427   /**
428    * Notice that the field are written in reverse order that the client can just
429    * pop items out of the stream.
430    */

431   private void writeHeader(StringBuffer JavaDoc buffer) {
432     buffer.append(",");
433     buffer.append(getFlags());
434     buffer.append(",");
435     buffer.append(SERIALIZATION_STREAM_VERSION);
436   }
437
438   private void writePayload(StringBuffer JavaDoc buffer) {
439     for (int i = tokenList.size() - 1; i >= 0; --i) {
440       String JavaDoc token = (String JavaDoc) tokenList.get(i);
441       buffer.append(token);
442       if (i > 0) {
443         buffer.append(",");
444       }
445     }
446   }
447
448   private void writeStringTable(StringBuffer JavaDoc buffer) {
449     if (tokenList.size() > 0) {
450       buffer.append(",");
451     }
452     buffer.append("[");
453     for (int i = 0, c = stringTable.size(); i < c; ++i) {
454       if (i > 0) {
455         buffer.append(",");
456       }
457       buffer.append(escapeString((String JavaDoc) stringTable.get(i)));
458     }
459     buffer.append("]");
460   }
461 }
462
Popular Tags