KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > user > rebind > rpc > SerializableTypeOracleBuilder


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.rebind.rpc;
17
18 import com.google.gwt.core.ext.BadPropertyValueException;
19 import com.google.gwt.core.ext.PropertyOracle;
20 import com.google.gwt.core.ext.TreeLogger;
21 import com.google.gwt.core.ext.UnableToCompleteException;
22 import com.google.gwt.core.ext.typeinfo.JArrayType;
23 import com.google.gwt.core.ext.typeinfo.JClassType;
24 import com.google.gwt.core.ext.typeinfo.JField;
25 import com.google.gwt.core.ext.typeinfo.JMethod;
26 import com.google.gwt.core.ext.typeinfo.JParameter;
27 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
28 import com.google.gwt.core.ext.typeinfo.JType;
29 import com.google.gwt.core.ext.typeinfo.NotFoundException;
30 import com.google.gwt.core.ext.typeinfo.TypeOracle;
31 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
32 import com.google.gwt.user.client.rpc.IsSerializable;
33 import com.google.gwt.user.client.rpc.SerializationStreamReader;
34 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
35
36 import java.io.Serializable JavaDoc;
37 import java.text.MessageFormat JavaDoc;
38 import java.util.ArrayList JavaDoc;
39 import java.util.Arrays JavaDoc;
40 import java.util.Collection JavaDoc;
41 import java.util.Comparator JavaDoc;
42 import java.util.HashMap JavaDoc;
43 import java.util.Iterator JavaDoc;
44 import java.util.List JavaDoc;
45 import java.util.Map JavaDoc;
46 import java.util.Set JavaDoc;
47 import java.util.Stack JavaDoc;
48 import java.util.TreeSet JavaDoc;
49
50 /**
51  * Builds a {@link SerializableTypeOracle} for a given
52  * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
53  *
54  * <p/> {@link java.lang.Object Object} is never serializable.
55  *
56  * <p/>A type is serializable if either of the following apply:
57  * <ul>
58  * <li>It is automatically serializable</li>
59  * <li>It is manually serializable</li>
60  * </ul>
61  *
62  * <p/> A class qualifies for automatic serialization if it:
63  * <ul>
64  * <li>Is a primitive type</li>
65  * <li>Is {@link java.lang.String String}</li>
66  * <li>Is assignable to {@link IsSerializable} or
67  * {@link java.io.Serializable Serializable}</li>
68  * </ul>
69  *
70  * <p/> It is an error if any automatically serializable class:
71  * <ul>
72  * <li>Is not default instantiable</li>
73  * <li>Has a non-static, non-transient, non-final field whose type is not
74  * serializable</li>
75  * </ul>
76  *
77  * <p/> A class qualifies for manual serialization if:
78  * <ul>
79  * <li>There is another class whose name is: className +
80  * "_CustomFieldSerializer"</li>
81  * </ul>
82  *
83  * <p/> It is an error if any manually serializable class:
84  * <ul>
85  * <li>It does not implement a deserialize method whose signature is: 'public
86  * static void deserialize({@link SerializationStreamReader}, &lt;T&gt;
87  * instance)'</li>
88  * <li>It does not implement a serializer method whose signature is: 'public
89  * static void serialize({@link SerializationStreamWriter}, &lt;T&gt;
90  * instance)'</li>
91  * <li>It is not default instantiable and the custom field serializer does not
92  * implement an instantiate method whose signature is: 'public static &lt;T&gt;
93  * instantiate(SerializationStreamReader)'</li>
94  * </ul>
95  *
96  * <p/> It is a warning if any serializable type:
97  * <ul>
98  * <li>Has final fields</li>
99  * <li>Has native methods</li>
100  * <li>Is assignable to {@link Collection} or {@link Map} but it is not a
101  * parameterized type</li>
102  * <li>Is automatically serializable and one of its subtypes is not;
103  * this warning can be treated as an error if the
104  * gwt.allowUnserializableSubtypesOfAutoSerializableTypes property is set to
105  * <code>false</code></li>
106  * </ul>
107  */

108 public class SerializableTypeOracleBuilder {
109   /**
110    * Represents the state of a type while we are determining the set of
111    * serializable types.
112    */

113   static final class TypeState {
114     private final String JavaDoc state;
115
116     protected TypeState(String JavaDoc state) {
117       this.state = state;
118     }
119
120     public String JavaDoc toString() {
121       return state;
122     }
123   }
124
125   /**
126    * Represents additional information about a type with regards to its
127    * serializability.
128    */

129   private class MetaTypeInfo {
130     /**
131      * <code>true</code> if the type is assignable to {@link IsSerializable}
132      * or {@link java.io.Serializable Serializable}.
133      */

134     private boolean autoSerializable;
135
136     /**
137      * <code>true</code> if the type was checked from a custom field
138      * serializer.
139      */

140     private boolean checkedInManualContext;
141
142     /**
143      * <code>true</code> if the subtypes of this type were also checked.
144      */

145     private boolean checkedSubtypes;
146
147     /**
148      * List of serialization failures.
149      */

150     private Set JavaDoc /* <String> */failures;
151
152     /**
153      * Custom field serializer or <code>null</code> if there isn't one.
154      */

155     private JClassType manualSerializer;
156
157     /**
158      * <code>true</code> if the type is automatically or manually serializable
159      * and the corresponding checks succeed.
160      */

161     private boolean serializable;
162
163     /**
164      * The state that this type is currently in.
165      */

166     private TypeState state = SerializableTypeOracleBuilder.NOT_CHECKED;
167
168     /**
169      * {@link JType} associated with this metadata.
170      */

171     private final JType type;
172
173     public MetaTypeInfo(JType type) {
174       this.type = type;
175
176       JClassType classOrInterface = type.isClassOrInterface();
177       if (classOrInterface != null) {
178         autoSerializable = classOrInterface.isAssignableTo(isSerializableClass)
179             || classOrInterface.isAssignableTo(serializableClass);
180         manualSerializer = findCustomFieldSerializer(typeOracle,
181             classOrInterface);
182       }
183     }
184
185     public void addFailure(String JavaDoc message) {
186       if (failures == null) {
187         failures = new TreeSet JavaDoc/* <String> */();
188       }
189
190       failures.add(message);
191     }
192
193     public boolean getCheckedInManualContext() {
194       return checkedInManualContext;
195     }
196
197     public boolean getCheckedSubtypes() {
198       return checkedSubtypes;
199     }
200
201     public Set JavaDoc /* <String> */getFailures() {
202       return failures;
203     }
204
205     public JClassType getManualSerializer() {
206       return manualSerializer;
207     }
208
209     public TypeState getState() {
210       return state;
211     }
212
213     public JType getType() {
214       return type;
215     }
216
217     public boolean isSerializable() {
218       if (state == SerializableTypeOracleBuilder.CHECK_IN_PROGRESS) {
219         // Assume that we are serializable if we are currently checking the
220
// type
221
return true;
222       }
223
224       return serializable && failures == null;
225     }
226
227     /**
228      * Returns <code>true</code> if this type needs to be rechecked. This
229      * happens if we are asked to check the subtypes and we did not previously
230      * check them. Or if this type is not serializable and we previously checked
231      * this type from a type which used manual serialization.
232      *
233      * @param checkSubtypes <code>true</code> if we need to check the subtypes
234      * @param inManualContext <code>true</code> if we are checking this type
235      * from a manually serializable type
236      * @return <code>true</code> if this type needs to be rechecked
237      */

238     public boolean needToRecheck(boolean checkSubtypes, boolean inManualContext) {
239       assert (state == SerializableTypeOracleBuilder.CHECK_SUCCEEDED);
240
241       if (!qualifiesForSerialization() && !inManualContext
242           && getCheckedInManualContext()) {
243         return true;
244       }
245
246       if (checkSubtypes && !getCheckedSubtypes()) {
247         return true;
248       }
249
250       return false;
251     }
252
253     /**
254      * Returns <code>true</code> if the type is assignable to
255      * {@link IsSerializable} or {@link java.io.Serializable Serializable}.
256      *
257      * @return <code>true</code> if the type is assignable to
258      * {@link IsSerializable} or
259      * {@link java.io.Serializable Serializable}
260      */

261     public boolean qualifiesForAutoSerialization() {
262       return autoSerializable && manualSerializer == null;
263     }
264
265     /**
266      * Returns <code>true</code> if the type has a custom field serializer.
267      *
268      * @return <code>true</code> if the type has a custom field serializer
269      */

270     public boolean qualifiesForManualSerialization() {
271       return manualSerializer != null;
272     }
273
274     public boolean qualifiesForSerialization() {
275       return qualifiesForAutoSerialization()
276           || qualifiesForManualSerialization();
277     }
278
279     public void setCheckedInManualContext(boolean checkedInManualContext) {
280       this.checkedInManualContext = checkedInManualContext;
281     }
282
283     public void setCheckedSubtypes(boolean checkedSubtypes) {
284       this.checkedSubtypes = checkedSubtypes;
285     }
286
287     public void setSerializable(boolean serializable) {
288       this.serializable = serializable;
289     }
290
291     public void setState(TypeState newState) {
292       state = newState;
293     }
294   }
295
296   /**
297    * A serializability problem was discovered with the type.
298    */

299   private static final TypeState CHECK_FAILED = new TypeState("Check failed");
300
301   /**
302    * The serializability of a type is being checked.
303    */

304   private static final TypeState CHECK_IN_PROGRESS = new TypeState(
305       "Check in progress");
306
307   /**
308    * The serializability of a type has been determined and there were no errors.
309    */

310   private static final TypeState CHECK_SUCCEEDED = new TypeState(
311       "Check succeeded");
312
313   /**
314    * The serializability of a type has not been checked.
315    */

316   private static final TypeState NOT_CHECKED = new TypeState("Not checked");
317
318   /**
319    * Finds the custom field serializer for a given type.
320    *
321    * @param typeOracle
322    * @param type
323    * @return the custom field serializer for a type or <code>null</code> if
324    * there is not one
325    */

326   static JClassType findCustomFieldSerializer(TypeOracle typeOracle, JType type) {
327     JClassType classOrInterface = type.isClassOrInterface();
328     if (classOrInterface == null) {
329       return null;
330     }
331
332     String JavaDoc customFieldSerializerName = type.getQualifiedSourceName()
333         + "_CustomFieldSerializer";
334     JClassType customSerializer = typeOracle.findType(customFieldSerializerName);
335     if (customSerializer == null) {
336       // If the type is in the java.lang or java.util packages then it will be
337
// mapped into com.google.gwt.user.client.rpc.core package
338
customSerializer = typeOracle.findType("com.google.gwt.user.client.rpc.core."
339           + customFieldSerializerName);
340     }
341
342     return customSerializer;
343   }
344
345   private static JArrayType getArrayType(TypeOracle typeOracle, int rank,
346       JType component) {
347     assert (rank > 0);
348
349     JArrayType array = null;
350     for (int i = 0; i < rank; ++i) {
351       array = typeOracle.getArrayType(component);
352       component = array;
353     }
354
355     return array;
356   }
357
358   private static void logSerializableTypes(TreeLogger logger, JType[] types) {
359     logger = logger.branch(TreeLogger.DEBUG, "Identified " + types.length
360         + " serializable type" + ((types.length == 1) ? "" : "s"), null);
361
362     for (int i = 0; i < types.length; ++i) {
363       logger.branch(TreeLogger.DEBUG,
364           types[i].getParameterizedQualifiedSourceName(), null);
365     }
366   }
367
368   /**
369    * If <code>true</code> it is not an error if all of the subtypes of an
370    * automatically serializable type are not themselves serializable.
371    */

372   private boolean allowUnserializableSubtypesOfAutoSerializableTypes;
373
374   /**
375    * Cache of the {@link JClassType} for {@link Collection}.
376    */

377   private final JClassType collectionClass;
378
379   /**
380    * A stack of types whose fields we are currently checking.
381    */

382   private final Stack JavaDoc /* <JType> */contexts = new Stack JavaDoc();
383
384   /**
385    * Cache of the {@link JClassType} for {@link IsSerializable}.
386    */

387   private final JClassType isSerializableClass;
388
389   /**
390    * Cache of the {@link JClassType} for {@link Map}.
391    */

392   private final JClassType mapClass;
393
394   private final RemoteServiceAsyncValidator remoteServiceAsyncValidator;
395
396   private final TreeLogger rootLogger;
397
398   /**
399    * Cache of the {@link JClassType} for
400    * {@link java.io.Serializable Serializable}.
401    */

402   private final JClassType serializableClass;
403
404   /**
405    * Cache of the {@link JClassType} for {@link SerializationStreamReader}.
406    */

407   private final JClassType streamReaderClass;
408
409   /**
410    * Cache of the {@link JClassType} for {@link SerializationStreamWriter}.
411    */

412   private final JClassType streamWriterClass;
413
414   /**
415    * Cache of the {@link JClassType} for {@link String}.
416    */

417   private final JClassType stringClass;
418
419   /**
420    * If <code>true</code> we will not warn if a serializable type contains a
421    * non-static final field. We warn because these fields are not serialized.
422    */

423   private boolean suppressNonStaticFinalFieldWarnings;
424
425   private final TypeOracle typeOracle;
426
427   /**
428    * Map of {@link JType} to {@link MetaTypeInfo}.
429    */

430   private final Map JavaDoc /* <JType, MetaTypeInfo> */typeToMetaTypeInfo = new HashMap JavaDoc();
431
432   /**
433    * <code>true</code> if we encountered a violation of either automatic or
434    * manual serialization which should result in an error.
435    */

436   private boolean validationFailed;
437
438   /**
439    * Constructs a builder.
440    *
441    * @param rootLogger
442    * @param typeOracle
443    * @throws UnableToCompleteException if we fail to find one of our special
444    * types
445    */

446   public SerializableTypeOracleBuilder(TreeLogger rootLogger,
447       TypeOracle typeOracle) throws UnableToCompleteException {
448     this.rootLogger = rootLogger;
449     this.typeOracle = typeOracle;
450
451     try {
452       collectionClass = typeOracle.getType(Collection JavaDoc.class.getName());
453       isSerializableClass = typeOracle.getType(IsSerializable.class.getName());
454       mapClass = typeOracle.getType(Map JavaDoc.class.getName());
455       serializableClass = typeOracle.getType(Serializable.class.getName());
456       stringClass = typeOracle.getType(String JavaDoc.class.getName());
457       streamReaderClass = typeOracle.getType(SerializationStreamReader.class.getName());
458       streamWriterClass = typeOracle.getType(SerializationStreamWriter.class.getName());
459
460       // String is always serializable
461
MetaTypeInfo stringMti = getMetaTypeInfo(stringClass);
462       stringMti.setSerializable(true);
463
464       // IncompatibleRemoteServiceException is always serializable
465
MetaTypeInfo incompatibleRemoteServiceExceptionMti = getMetaTypeInfo(typeOracle.getType(IncompatibleRemoteServiceException.class.getName()));
466       incompatibleRemoteServiceExceptionMti.setSerializable(true);
467
468       remoteServiceAsyncValidator = new RemoteServiceAsyncValidator(typeOracle);
469     } catch (NotFoundException e) {
470       rootLogger.log(TreeLogger.ERROR, null, e);
471       throw new UnableToCompleteException();
472     }
473   }
474
475   /**
476    * Builds a {@link SerializableTypeOracle} for a give
477    * {@link com.google.gwt.user.client.rpc.RemoteService} interface.
478    *
479    * @throws UnableToCompleteException if the the remote service is considered
480    * invalid due to serialization problem or a missing or ill formed
481    * remote service asynchronous interface
482    */

483   public SerializableTypeOracle build(PropertyOracle propertyOracle,
484       JClassType remoteService) throws UnableToCompleteException {
485
486     initializeProperties(rootLogger, propertyOracle);
487
488     remoteServiceAsyncValidator.validateRemoteServiceAsync(rootLogger,
489         remoteService);
490
491     TreeLogger logger = rootLogger.branch(TreeLogger.DEBUG, "Analyzing '"
492         + remoteService.getParameterizedQualifiedSourceName()
493         + "' for serializable types", null);
494
495     validateRemoteService(logger, remoteService);
496
497     if (validationFailed) {
498       // the validation code has already logged why
499
throw new UnableToCompleteException();
500     }
501
502     List JavaDoc serializableTypesList = new ArrayList JavaDoc();
503     Iterator JavaDoc iterTypes = typeToMetaTypeInfo.values().iterator();
504     while (iterTypes.hasNext()) {
505       MetaTypeInfo mti = (MetaTypeInfo) iterTypes.next();
506       JType type = mti.getType();
507       if (mti.isSerializable() && type.isInterface() == null) {
508         serializableTypesList.add(type);
509       }
510     }
511
512     JType[] serializableTypes = new JType[serializableTypesList.size()];
513     serializableTypesList.toArray(serializableTypes);
514
515     Arrays.sort(serializableTypes, new Comparator JavaDoc() {
516       public int compare(Object JavaDoc o1, Object JavaDoc o2) {
517         String JavaDoc n1 = ((JType) o1).getQualifiedSourceName();
518         String JavaDoc n2 = ((JType) o2).getQualifiedSourceName();
519         return n1.compareTo(n2);
520       }
521     });
522
523     logSerializableTypes(logger, serializableTypes);
524
525     return new SerializableTypeOracleImpl(typeOracle, serializableTypes);
526   }
527
528   /**
529    * The component type of an array must be serializable.
530    */

531   private void checkArray(TreeLogger logger, JArrayType array) {
532     checkType(
533         logger.branch(TreeLogger.DEBUG, "Analyzing component type:", null),
534         array.getComponentType(), true);
535
536     JType leafType = array.getLeafType();
537     JClassType classOrInterface = leafType.isClassOrInterface();
538     if (leafType.isPrimitive() != null) {
539       getMetaTypeInfo(array).setSerializable(true);
540     } else if (classOrInterface != null) {
541       MetaTypeInfo mti = getMetaTypeInfo(classOrInterface);
542       if (mti.isSerializable()) {
543         getMetaTypeInfo(array).setSerializable(true);
544       }
545
546       JClassType[] subtypes = classOrInterface.getSubtypes();
547       for (int i = 0; i < subtypes.length; ++i) {
548         JClassType component = subtypes[i];
549         JArrayType covariantArray = getArrayType(typeOracle, array.getRank(),
550             component);
551
552         MetaTypeInfo cmti = getMetaTypeInfo(component);
553         if (cmti.isSerializable()) {
554           logger.branch(TreeLogger.DEBUG,
555               covariantArray.getParameterizedQualifiedSourceName(), null);
556           getMetaTypeInfo(covariantArray).setSerializable(true);
557         }
558       }
559     }
560   }
561
562   /**
563    * Case 1: Type is automatically serializable a) All fields must be
564    * serializable b) All subtypes must be serializable unless we allow subtypes
565    * that are not serializable c) If inherited automatic serialization
566    * superclass must be serializable
567    *
568    * Case 2: Type is manually serializable a) CSF must be valid b) All
569    * automatically and manually serializable fields must be serializable c) Any
570    * field that is not manually or automatically serializable is okay
571    *
572    * Case 3: Type is neither automatically or manually serializable a) If type
573    * has at least one automatically or manually serializable subtype then we are
574    * okay. b) If type has no serializable subtypes then: i) context is
575    * automatically serializable => error ii) context is manually serializable =>
576    * warning iii) context is neither manually nor automatically serializable =>
577    * warning.
578    */

579   private void checkClassOrInterface(TreeLogger logger, JClassType type,
580       boolean validateSubtypes) {
581     if (type == stringClass) {
582       // we know that it is serializable
583
return;
584     }
585
586     if (type == typeOracle.getJavaLangObject()) {
587       // Object is never serializable
588
setUnserializableAndLog(
589           logger,
590           inManualSerializationContext() ? TreeLogger.WARN : TreeLogger.ERROR,
591           "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
592           type);
593       return;
594     }
595
596     JClassType superclass = type.getSuperclass();
597     if (superclass != null) {
598       MetaTypeInfo smti = getMetaTypeInfo(superclass);
599       if (smti.qualifiesForSerialization()) {
600         checkType(
601             logger.branch(TreeLogger.DEBUG, "Analyzing superclass:", null),
602             superclass, false);
603       } else {
604         logger.branch(TreeLogger.DEBUG, "Not analyzing superclass '"
605             + superclass.getParameterizedQualifiedSourceName()
606             + "' because it is not assignable to '"
607             + IsSerializable.class.getName() + "' or '"
608             + Serializable.class.getName()
609             + "' nor does it have a custom field serializer", null);
610       }
611     }
612
613     MetaTypeInfo mti = getMetaTypeInfo(type);
614     if (mti.qualifiesForManualSerialization()) {
615       List JavaDoc failures = CustomFieldSerializerValidator.validate(
616           streamReaderClass, streamWriterClass, mti.getManualSerializer(), type);
617       if (!failures.isEmpty()) {
618         setUnserializableAndLog(logger, TreeLogger.ERROR, failures, type);
619         return;
620       }
621
622       mti.setSerializable(true);
623
624       checkFields(logger, type);
625
626     } else if (mti.qualifiesForAutoSerialization()) {
627       if (type.isLocalType()) {
628         setUnserializableAndLog(
629             logger,
630             TreeLogger.WARN,
631             "Is a local type, it will be excluded from the set of serializable types",
632             type);
633         return;
634       }
635
636       if (type.isMemberType() && !type.isStatic()) {
637         setUnserializableAndLog(
638             logger,
639             TreeLogger.WARN,
640             "Is nested but not static, it will be excluded from the set of serializable types",
641             type);
642         return;
643       }
644
645       if (type.isClass() != null && !type.isDefaultInstantiable()) {
646         setUnserializableAndLog(
647             logger,
648             TreeLogger.ERROR,
649             "Was not default instantiable (it must have a zero-argument public constructor or no constructors at all)",
650             type);
651         return;
652       }
653
654       if (type.isAbstract() && type.getSubtypes().length == 0) {
655         // Just ignore pure, abstract classes that have no subtypes
656
return;
657       }
658
659       getMetaTypeInfo(type).setSerializable(true);
660
661       checkMethods(logger, type);
662
663       checkFields(logger, type);
664     }
665
666     if (validateSubtypes) {
667       int nSubtypes = 0;
668       int nSerializableSubtypes = 0;
669
670       JClassType[] subtypes = type.getSubtypes();
671       if (subtypes.length > 0) {
672         TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
673             "Analyzing subclasses:", null);
674
675         for (int i = 0; i < subtypes.length; ++i) {
676           JClassType subtype = subtypes[i];
677           MetaTypeInfo smti = getMetaTypeInfo(subtype);
678           if (smti.qualifiesForSerialization()) {
679             checkType(localLogger, subtype, false);
680
681             ++nSubtypes;
682
683             if (smti.isSerializable()) {
684               ++nSerializableSubtypes;
685             } else {
686               localLogger.branch(TreeLogger.DEBUG,
687                   subtype.getParameterizedQualifiedSourceName()
688                       + " is not serializable", null);
689
690               if (subtype.isLocalType() || subtype.isMemberType()
691                   && !subtype.isStatic()) {
692                 --nSubtypes;
693               }
694             }
695           } else {
696             localLogger.branch(TreeLogger.DEBUG, "Not analyzing subclass '"
697                 + subtype.getParameterizedQualifiedSourceName()
698                 + "' because it is not assignable to '"
699                 + IsSerializable.class.getName() + "' or '"
700                 + Serializable.class.getName()
701                 + "' nor does it have a custom field serializer", null);
702           }
703         }
704       }
705
706       if (mti.qualifiesForAutoSerialization()) {
707         if (nSerializableSubtypes < nSubtypes) {
708           setUnserializableAndLog(logger,
709               allowUnserializableSubtypesOfAutoSerializableTypes
710                   ? TreeLogger.WARN : TreeLogger.ERROR,
711               "Not all subtypes of the automatically serializable type '"
712                   + type.getQualifiedSourceName()
713                   + "' are themselves automatically serializable", type);
714         }
715       } else if (!mti.qualifiesForManualSerialization()
716           && nSerializableSubtypes == 0) {
717         /*
718          * The type does not qualify for either serialization and it has no
719          * serializable subtypes; this is only an error if we are not in the
720          * context of a custom field serializer
721          */

722         String JavaDoc message = MessageFormat.format(
723             "Type ''{0}'' is not assignable to IsSerializable or java.io.Serializable, it does not have a custom field serializer and it does not have any serializable subtypes",
724             new String JavaDoc[] {type.getParameterizedQualifiedSourceName()});
725         setUnserializableAndLog(logger, inManualSerializationContext()
726             ? TreeLogger.WARN : TreeLogger.ERROR, message, type);
727       }
728     }
729   }
730
731   private void checkFields(TreeLogger logger, JClassType classOrInterface) {
732     TreeLogger localLogger = logger;
733     JField[] fields = classOrInterface.getFields();
734     if (fields.length > 0) {
735       localLogger = localLogger.branch(TreeLogger.DEBUG, "Analyzing Fields:",
736           null);
737
738       contexts.push(classOrInterface);
739
740       for (int i = 0; i < fields.length; ++i) {
741         JField field = fields[i];
742
743         if (field.isStatic() || field.isTransient()) {
744           continue;
745         }
746
747         if (field.isFinal()) {
748           if (!suppressNonStaticFinalFieldWarnings) {
749             localLogger.branch(TreeLogger.WARN, "Field '" + field.toString()
750                 + "' will not be serialized because it is final", null);
751           }
752           continue;
753         }
754
755         TreeLogger fieldLogger = localLogger.branch(TreeLogger.DEBUG,
756             field.toString(), null);
757         JType fieldType = field.getType();
758         checkForUnparameterizedType(fieldLogger, fieldType);
759         checkType(fieldLogger, fieldType, true);
760       }
761
762       contexts.pop();
763
764     } else {
765       localLogger.branch(TreeLogger.DEBUG, "No fields to analyze", null);
766     }
767   }
768
769   private void checkForUnparameterizedType(TreeLogger logger, JType type) {
770     if (type.isParameterized() != null) {
771       return;
772     }
773
774     JClassType classOrInterface = type.isClassOrInterface();
775     if (classOrInterface != null) {
776       if (classOrInterface.isAssignableTo(collectionClass)
777           || classOrInterface.isAssignableTo(mapClass)) {
778         TreeLogger localLogger = logger.branch(
779             TreeLogger.WARN,
780             "Type '"
781                 + type.getQualifiedSourceName()
782                 + "' should be parameterized to help the compiler produce the smallest code size possible for your module. Since the gwt.typeArgs javadoc annotation is missing, all subtypes of Object will be analyzed for serializability even if they are not directly or indirectly used",
783             null);
784
785         /*
786          * This will pull in the world and the set of serializable types will be
787          * larger than it needs to be. We exclude types that do not qualify for
788          * serialization to avoid generating false errors due to types that do
789          * not qualify for serialization and have no serializable subtypes.
790          */

791         JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
792         for (int i = 0; i < allTypes.length; ++i) {
793           JClassType cls = allTypes[i];
794           MetaTypeInfo mti = getMetaTypeInfo(cls);
795           if (mti.qualifiesForSerialization()) {
796             checkType(localLogger, cls, true);
797           }
798         }
799       }
800     }
801   }
802
803   private void checkMethods(TreeLogger logger, JClassType classOrInterface) {
804     JMethod[] methods = classOrInterface.getMethods();
805     for (int i = 0; i < methods.length; ++i) {
806       if (methods[i].isNative()) {
807         logger.branch(
808             TreeLogger.WARN,
809             MessageFormat.format(
810                 "Method ''{0}'' is native, calling this method in server side code will result in an UnsatisfiedLinkError",
811                 new String JavaDoc[] {methods[i].toString()}), null);
812       }
813     }
814   }
815
816   /**
817    * Checks that a type which qualifies as either automatically serializable or
818    * manually serializable actually is.
819    */

820   private void checkType(TreeLogger logger, JType type, boolean checkSubtypes) {
821     if (type == null || type.isPrimitive() != null) {
822       return;
823     }
824
825     logger = logger.branch(TreeLogger.DEBUG,
826         type.getParameterizedQualifiedSourceName(), null);
827
828     MetaTypeInfo mti = getMetaTypeInfo(type);
829     TypeState state = mti.getState();
830
831     if (state == SerializableTypeOracleBuilder.CHECK_FAILED) {
832       logReasonsForUnserializability(logger, type);
833       return;
834     } else if (state == SerializableTypeOracleBuilder.CHECK_IN_PROGRESS) {
835       logger.branch(TreeLogger.DEBUG, "'"
836           + type.getParameterizedQualifiedSourceName()
837           + "' is being analyzed; skipping", null);
838       return;
839     } else if (state == SerializableTypeOracleBuilder.CHECK_SUCCEEDED) {
840       if (!mti.needToRecheck(checkSubtypes, inManualSerializationContext())) {
841         logger.branch(TreeLogger.DEBUG, "Type has already been analyzed", null);
842         return;
843       }
844     }
845
846     mti.setState(SerializableTypeOracleBuilder.CHECK_IN_PROGRESS);
847
848     if (type.isParameterized() != null) {
849       JParameterizedType parameterized = type.isParameterized();
850       checkType(logger.branch(TreeLogger.DEBUG, "Analyzing raw type", null),
851           parameterized.getRawType(), true);
852
853       checkTypes(logger.branch(TreeLogger.DEBUG, "Analyzing type args", null),
854           parameterized.getTypeArgs());
855     } else if (type.isArray() != null) {
856       checkArray(logger, type.isArray());
857     } else if (type.isClassOrInterface() != null) {
858       checkClassOrInterface(logger, type.isClassOrInterface(), checkSubtypes);
859     }
860
861     if (mti.getState() != SerializableTypeOracleBuilder.CHECK_FAILED) {
862       mti.setState(SerializableTypeOracleBuilder.CHECK_SUCCEEDED);
863     }
864
865     mti.setCheckedSubtypes(checkSubtypes);
866     mti.setCheckedInManualContext(inManualSerializationContext());
867   }
868
869   private void checkTypes(TreeLogger logger, JType[] types) {
870     for (int i = 0; i < types.length; ++i) {
871       checkType(logger, types[i], true);
872     }
873   }
874
875   private MetaTypeInfo getMetaTypeInfo(JType type) {
876     MetaTypeInfo mti = (MetaTypeInfo) typeToMetaTypeInfo.get(type);
877     if (mti == null) {
878       mti = new MetaTypeInfo(type);
879       typeToMetaTypeInfo.put(type, mti);
880     }
881
882     return mti;
883   }
884
885   private void initializeProperties(TreeLogger logger,
886       PropertyOracle propertyOracle) {
887     // Assume subtype warnings unless user explicitly turns it off.
888
allowUnserializableSubtypesOfAutoSerializableTypes = false;
889
890     try {
891       String JavaDoc propVal = propertyOracle.getPropertyValue(logger,
892           "gwt.allowUnserializableSubtypesOfAutoSerializableTypes");
893       if (propVal.equals("true")) {
894         allowUnserializableSubtypesOfAutoSerializableTypes = true;
895       }
896     } catch (BadPropertyValueException e) {
897       // Purposely ignored, because we do not want to allow subtypes that are
898
// not serializable by default.
899
}
900
901     suppressNonStaticFinalFieldWarnings = false;
902     try {
903       String JavaDoc propVal = propertyOracle.getPropertyValue(logger,
904           "gwt.suppressNonStaticFinalFieldWarnings");
905       if (propVal.equals("true")) {
906         suppressNonStaticFinalFieldWarnings = true;
907       }
908     } catch (BadPropertyValueException e) {
909       // Purposely ignored, because we do want to warn if non-static, final
910
// are part of a serializable type
911
}
912   }
913
914   /**
915    * Returns <code>true</code> if the type which caused us to analyze the
916    * current type uses manual serialization.
917    *
918    * @return <code>true</code> if the type which caused us to analyze the
919    * current type uses manual serialization
920    */

921   private boolean inManualSerializationContext() {
922     if (contexts.isEmpty()) {
923       return false;
924     }
925
926     JType parent = (JType) contexts.peek();
927     JClassType parentClass = parent.isClassOrInterface();
928
929     if (parentClass != null) {
930       return getMetaTypeInfo(parentClass).qualifiesForManualSerialization();
931     }
932
933     return false;
934   }
935
936   private void logReasonsForUnserializability(TreeLogger logger, JType type) {
937     TreeLogger.Type logType;
938     MetaTypeInfo mti = getMetaTypeInfo(type);
939
940     boolean autoSerializable = mti.qualifiesForAutoSerialization();
941     if (inManualSerializationContext() && !autoSerializable) {
942       logType = TreeLogger.WARN;
943     } else {
944       logType = TreeLogger.ERROR;
945
946       if (autoSerializable) {
947         JClassType classOrInterface = type.isClassOrInterface();
948
949         if (classOrInterface.isLocalType()
950             || (classOrInterface.isMemberType() && !classOrInterface.isStatic())) {
951           logType = TreeLogger.WARN;
952         }
953       }
954     }
955
956     if (logType == TreeLogger.ERROR) {
957       validationFailed = true;
958     }
959
960     Set JavaDoc /* <String> */reasons = mti.getFailures();
961     Iterator JavaDoc iter = reasons.iterator();
962     while (iter.hasNext()) {
963       logger.branch(logType, (String JavaDoc) iter.next(), null);
964     }
965   }
966
967   private void setUnserializableAndLog(TreeLogger logger,
968       TreeLogger.Type logType, List JavaDoc failures, JClassType type) {
969     Iterator JavaDoc iter = failures.iterator();
970     while (iter.hasNext()) {
971       setUnserializableAndLog(logger, logType, (String JavaDoc) iter.next(), type);
972     }
973   }
974
975   private void setUnserializableAndLog(TreeLogger logger,
976       TreeLogger.Type logType, String JavaDoc message, JType type) {
977     MetaTypeInfo mti = getMetaTypeInfo(type);
978     mti.setState(SerializableTypeOracleBuilder.CHECK_FAILED);
979     mti.setSerializable(false);
980     mti.addFailure(message);
981
982     if (logType == TreeLogger.ERROR) {
983       validationFailed = true;
984     }
985
986     logger.branch(logType, message, null);
987   }
988
989   private void validateRemoteService(TreeLogger logger, JClassType remoteService) {
990     JMethod[] methods = remoteService.getOverridableMethods();
991
992     logger = logger.branch(TreeLogger.DEBUG, "Analyzing methods:", null);
993
994     for (int i = 0; i < methods.length; ++i) {
995       JMethod method = methods[i];
996       TreeLogger methodLogger = logger.branch(TreeLogger.DEBUG,
997           method.toString(), null);
998       JType returnType = method.getReturnType();
999       TreeLogger returnTypeLogger = methodLogger.branch(TreeLogger.DEBUG,
1000          "Return type: " + returnType.getParameterizedQualifiedSourceName(),
1001          null);
1002      checkForUnparameterizedType(returnTypeLogger, returnType);
1003      checkType(returnTypeLogger, returnType, true);
1004
1005      JParameter[] params = method.getParameters();
1006      for (int j = 0; j < params.length; ++j) {
1007        JParameter param = params[j];
1008        TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG,
1009            "Parameter: " + param.toString(), null);
1010        JType paramType = param.getType();
1011        checkForUnparameterizedType(paramLogger, paramType);
1012        checkType(paramLogger, paramType, true);
1013      }
1014
1015      JType[] exs = method.getThrows();
1016      if (exs.length > 0) {
1017        checkTypes(methodLogger.branch(TreeLogger.DEBUG, "Throws:", null),
1018            method.getThrows());
1019      }
1020    }
1021  }
1022}
1023
Popular Tags