001    /**
002     * Copyright (C) 2009-2011 the original author or authors.
003     * See the notice.md file distributed with this work for additional
004     * information regarding copyright ownership.
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.fusesource.restygwt.rebind;
020    
021    import java.lang.annotation.Annotation;
022    import java.util.ArrayList;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.codehaus.jackson.annotate.JsonCreator;
027    import org.codehaus.jackson.annotate.JsonProperty;
028    import org.codehaus.jackson.annotate.JsonSubTypes;
029    import org.codehaus.jackson.annotate.JsonTypeInfo;
030    import org.codehaus.jackson.annotate.JsonTypeInfo.As;
031    import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
032    import org.codehaus.jackson.annotate.JsonTypeName;
033    import org.codehaus.jackson.map.annotate.JsonTypeIdResolver;
034    import org.codehaus.jackson.map.jsontype.TypeIdResolver;
035    import org.fusesource.restygwt.client.Json;
036    import org.fusesource.restygwt.client.Json.Style;
037    
038    import com.google.gwt.core.ext.BadPropertyValueException;
039    import com.google.gwt.core.ext.GeneratorContext;
040    import com.google.gwt.core.ext.TreeLogger;
041    import com.google.gwt.core.ext.UnableToCompleteException;
042    import com.google.gwt.core.ext.typeinfo.JClassType;
043    import com.google.gwt.core.ext.typeinfo.JConstructor;
044    import com.google.gwt.core.ext.typeinfo.JField;
045    import com.google.gwt.core.ext.typeinfo.JParameter;
046    import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
047    import com.google.gwt.core.ext.typeinfo.JType;
048    import com.google.gwt.json.client.JSONArray;
049    import com.google.gwt.json.client.JSONObject;
050    import com.google.gwt.json.client.JSONValue;
051    import com.google.gwt.thirdparty.guava.common.collect.Lists;
052    import com.google.gwt.thirdparty.guava.common.collect.Maps;
053    import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
054    
055    /**
056     * 
057     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
058     * 
059     *         Updates: added getter & setter support, enhanced generics support
060     * @author <a href="http://www.acuedo.com">Dave Finch</a>
061     * 
062     *         added polymorphic support
063     * @author <a href="http://charliemason.info">Charlie Mason</a>
064     * 
065     */
066    
067    public class JsonEncoderDecoderClassCreator extends BaseSourceCreator {
068        private static final String JSON_ENCODER_SUFFIX = "_Generated_JsonEncoderDecoder_";
069    
070        private String JSON_ENCODER_DECODER_CLASS = JsonEncoderDecoderInstanceLocator.JSON_ENCODER_DECODER_CLASS;
071        private static final String JSON_VALUE_CLASS = JSONValue.class.getName();
072        private static final String JSON_OBJECT_CLASS = JSONObject.class.getName();
073        private static final String JSON_ARRAY_CLASS = JSONArray.class.getName();
074    
075        JsonEncoderDecoderInstanceLocator locator;
076    
077        public JsonEncoderDecoderClassCreator(TreeLogger logger, GeneratorContext context, JClassType source) throws UnableToCompleteException {
078            super(logger, context, source, JSON_ENCODER_SUFFIX);
079        }
080    
081        @Override
082        protected ClassSourceFileComposerFactory createComposerFactory() {
083            ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, shortName);
084            composerFactory.setSuperclass(JSON_ENCODER_DECODER_CLASS + "<" + source.getParameterizedQualifiedSourceName() + ">");
085            return composerFactory;
086        }
087    
088        private static class Subtype {
089            final String tag;
090            final JClassType clazz;
091    
092            public Subtype(String tag, JClassType clazz) {
093                this.tag = tag;
094                this.clazz = clazz;
095            }
096        }
097    
098        private <T extends Annotation> T findAnnotation(JClassType clazz, Class<T> annotation) {
099            if (clazz == null)
100                return null;
101            else if (clazz.isAnnotationPresent(annotation))
102                return clazz.getAnnotation(annotation);
103            else
104                return findAnnotation(clazz.getSuperclass(), annotation);
105        }
106    
107        @Override
108        public void generate() throws UnableToCompleteException {
109            final List<Subtype> possibleTypes = Lists.newArrayList();
110            final JsonTypeInfo typeInfo = findAnnotation(source, JsonTypeInfo.class);
111            final boolean isLeaf = !source.isAnnotationPresent(JsonTypeInfo.class);
112            if (typeInfo != null) {
113                final JsonSubTypes jacksonSubTypes = findAnnotation(source, JsonSubTypes.class);
114                if (typeInfo.use() == Id.CLASS || typeInfo.use() == Id.MINIMAL_CLASS) {
115                    if (jacksonSubTypes != null) {
116                        for (JsonSubTypes.Type type : jacksonSubTypes.value()) {
117                            JClassType typeClass = find(type.value());
118                            if (!isLeaf || source.equals(typeClass))
119                                possibleTypes.add(new Subtype(typeInfo.use() == Id.CLASS ? typeClass.getQualifiedSourceName() : typeClass.getSimpleSourceName(), typeClass));
120                        }
121                    } else {
122                        error("@JsonSubTypes annotation missing for type: " + source);
123                    }
124                } else if (typeInfo.use() != Id.NONE) {
125                    final JsonTypeIdResolver typeResolver = findAnnotation(source, JsonTypeIdResolver.class);
126                    if (jacksonSubTypes != null) {
127                        for (JsonSubTypes.Type type : jacksonSubTypes.value()) {
128                            if (type.name() != null && !type.name().isEmpty()) {
129                                JClassType typeClass = find(type.value());
130                                if (!isLeaf || source.equals(typeClass))
131                                    possibleTypes.add(new Subtype(type.name(), typeClass));
132                            } else {
133                                JsonTypeName nameAnnotation = type.value().getAnnotation(JsonTypeName.class);
134                                if (nameAnnotation == null || nameAnnotation.value() == null || nameAnnotation.value().isEmpty())
135                                    error("Cannot find @JsonTypeName annotation for type: " + type.value());
136                                JClassType typeClass = find(type.value());
137                                if (!isLeaf || source.equals(typeClass))
138                                    possibleTypes.add(new Subtype(nameAnnotation.value(), typeClass));
139                            }
140                        }
141                        if (isLeaf && possibleTypes.size() == 0)
142                            error("Could not find @JsonSubTypes entry for type: " + source);
143                    } else if (typeResolver != null) {
144                        Class<? extends TypeIdResolver> resolverClass = typeResolver.value();
145                        RestyJsonTypeIdResolver restyResolver;
146                        if (RestyJsonTypeIdResolver.class.isAssignableFrom(resolverClass)) {
147                            try {
148                                restyResolver = (RestyJsonTypeIdResolver) resolverClass.newInstance();
149                            } catch (Exception e) {
150                                logger.log(ERROR, "Could not acccess: " + resolverClass, e);
151                                throw new UnableToCompleteException();
152                            }
153                        } else {
154                            restyResolver = getRestyResolverClassMap(context, logger).get(resolverClass);
155                            if (restyResolver == null)
156                                error("Could not find RestyJsonTypeIdResolver for " + resolverClass + " did you forget to put <extend-configuration-property name=\"org.fusesource.restygwt.jsontypeidresolver\" value=\"<fully-qualified-class-implementing-RestyJsonTypeIdResolver>\"/> in your *.gwt.xml?");
157        
158                        }
159        
160                        for (Map.Entry<String, Class<?>> entry : restyResolver.getIdClassMap().entrySet()) {
161                            JClassType entryType = find(entry.getValue());
162                            if (!isLeaf || source.equals(entryType))
163                                possibleTypes.add(new Subtype(entry.getKey(), entryType));
164                        }
165                        if (isLeaf && possibleTypes.size() == 0)
166                            error("Could not find entry in " + restyResolver.getClass().getName() + " for type: " + source);
167                    } else {
168                        error("Cannot find required subtype resolution for type: " + source);
169                    }
170                } else {
171                    error("Id.NONE not supported");
172                }
173            } else {
174                possibleTypes.add(new Subtype(null, source));
175            }
176            locator = new JsonEncoderDecoderInstanceLocator(context, logger);
177        
178            JClassType sourceClazz = source.isClass();
179            if (sourceClazz == null) {
180                error("Type is not a class");
181            }
182        
183        
184            if (sourceClazz.isAbstract()) {
185                if (typeInfo == null) {
186                    error("Abstract classes must be annotated with JsonTypeInfo");
187                }
188            }
189            Json jsonAnnotation = source.getAnnotation(Json.class);
190            final Style classStyle = jsonAnnotation != null ? jsonAnnotation.style() : Style.DEFAULT;
191            final String railsWrapperName = jsonAnnotation != null && jsonAnnotation.name().length() > 0 ? jsonAnnotation.name() : sourceClazz.getName().toLowerCase();
192        
193            p();
194            p("public static final " + shortName + " INSTANCE = new " + shortName + "();");
195            p();
196        
197            if (null != sourceClazz.isEnum()) {
198                p();
199                p("public " + JSON_VALUE_CLASS + " encode(" + source.getParameterizedQualifiedSourceName() + " value) {").i(1);
200                {
201                    p("if( value==null ) {").i(1);
202                    {
203                        p("return com.google.gwt.json.client.JSONNull.getInstance();").i(-1);
204                    }
205                    p("}");
206                    p("return new com.google.gwt.json.client.JSONString(value.name());");
207                }
208                i(-1).p("}");
209                p();
210                p("public " + source.getName() + " decode(" + JSON_VALUE_CLASS + " value) {").i(1);
211                {
212                    p("if( value == null || value.isNull()!=null ) {").i(1);
213                    {
214                        p("return null;").i(-1);
215                    }
216                    p("}");
217                    p("com.google.gwt.json.client.JSONString str = value.isString();");
218                    p("if( null == str ) {").i(1);
219                    {
220                        p("throw new DecodingException(\"Expected a json string (for enum), but was given: \"+value);").i(-1);
221                    }
222                    p("}");
223                    p("return Enum.valueOf(" + source.getParameterizedQualifiedSourceName() + ".class, str.stringValue());").i(-1);
224                }
225                p("}");
226                p();
227                return;
228            }
229        
230            p("public " + JSON_VALUE_CLASS + " encode(" + source.getParameterizedQualifiedSourceName() + " value) {").i(1);
231            {
232                p("if( value==null ) {").i(1);
233                {
234                    p("return null;");
235                }
236                i(-1).p("}");
237        
238                boolean returnWrapper = false; // if set, return rrc
239        
240                p(JSON_OBJECT_CLASS + " rc = new " + JSON_OBJECT_CLASS + "();");
241                if (classStyle == Style.RAILS) {
242                    returnWrapper = true;
243                    p(JSON_OBJECT_CLASS + " rrc = new " + JSON_OBJECT_CLASS + "();");
244                    p("rrc.put(\"" + railsWrapperName + "\" , rc);");
245                }
246        
247                for (Subtype possibleType : possibleTypes) {
248                    // Try to find a constuctor that is annotated as creator
249                    final JConstructor creator = findCreator(possibleType.clazz);
250                    
251                    List<JField> orderedFields = creator == null ? null : getOrderedFields(getFields(possibleType.clazz), creator);
252        
253                    if (!isLeaf) {
254                        // Generate a decoder for each possible type
255                        p("if(value.getClass().getName().equals(\"" + possibleType.clazz.getQualifiedBinaryName() + "\"))");
256                        p("{");
257                    }
258        
259                    if (typeInfo != null) {
260                        switch (typeInfo.include()) {
261                        case PROPERTY:
262                            p("com.google.gwt.json.client.JSONValue className=org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.encode(\"" + possibleType.tag + "\");");
263                            p("if( className!=null ) { ").i(1);
264                            p("rc.put(\"" + typeInfo.property() + "\", className);");
265                            i(-1).p("}");
266                            break;
267                        case WRAPPER_OBJECT:
268                            returnWrapper = true;
269                            p(JSON_OBJECT_CLASS + " rrc = new " + JSON_OBJECT_CLASS + "();");
270                            p("rrc.put(\"" + possibleType.tag + "\", rc);");
271                            break;
272                        case WRAPPER_ARRAY:
273                            returnWrapper = true;
274                            p(JSON_ARRAY_CLASS + " rrc = new " + JSON_ARRAY_CLASS + "();");
275                            p("rrc.set(0, org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.encode(\"" + possibleType.tag + "\"));");
276                            p("rrc.set(1, rc);");
277                        }
278                    }
279        
280                    p(possibleType.clazz.getParameterizedQualifiedSourceName() + " parseValue = (" + possibleType.clazz.getParameterizedQualifiedSourceName() + ")value;");
281        
282                    for (final JField field : getFields(possibleType.clazz)) {
283        
284                        final String getterName = getGetterName(field);
285        
286                        // If can ignore some fields right off the back..
287                        // if there is a creator encode only final fields with JsonProperty annotation
288                        if (getterName == null && (field.isStatic() || (field.isFinal() && !(creator != null && orderedFields.contains(field))) || field.isTransient())) {
289                            continue;
290                        }
291        
292                        branch("Processing field: " + field.getName(), new Branch<Void>() {
293                            public Void execute() throws UnableToCompleteException {
294                                // TODO: try to get the field with a setter or
295                                // JSNI
296                                if (getterName != null || field.isDefaultAccess() || field.isProtected() || field.isPublic()) {
297        
298                                    Json jsonAnnotation = field.getAnnotation(Json.class);
299        
300                                    String name = field.getName();
301                                    String jsonName = name;
302        
303                                    if (jsonAnnotation != null && jsonAnnotation.name().length() > 0) {
304                                        jsonName = jsonAnnotation.name();
305                                    }
306        
307                                    String fieldExpr = "parseValue." + name;
308                                    if (getterName != null) {
309                                        fieldExpr = "parseValue." + getterName + "()";
310                                    }
311        
312                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
313                                    String expression = locator.encodeExpression(field.getType(), fieldExpr, style);
314        
315                                    p("{").i(1);
316                                    {
317                                        if (null != field.getType().isEnum()) {
318                                            p("if(" + fieldExpr + " == null) {").i(1);
319                                            p("rc.put(" + wrap(name) + ", null);");
320                                            i(-1).p("} else {").i(1);
321                                        }
322        
323                                        p(JSON_VALUE_CLASS + " v=" + expression + ";");
324                                        p("if( v!=null ) {").i(1);
325                                        {
326                                            p("rc.put(" + wrap(jsonName) + ", v);");
327                                        }
328                                        i(-1).p("}");
329        
330                                        if (null != field.getType().isEnum()) {
331                                            i(-1).p("}");
332                                        }
333        
334                                    }
335                                    i(-1).p("}");
336        
337                                } else {
338                                    error("field must not be private: " + field.getEnclosingType().getQualifiedSourceName() + "." + field.getName());
339                                }
340                                return null;
341                            }
342                        });
343        
344                    }
345        
346                    if (returnWrapper) {
347                        p("return rrc;");
348                    } else {
349                        p("return rc;");
350                    }
351        
352                    if (!isLeaf) {
353                        p("}");
354                    }
355                }
356        
357                if (!isLeaf) {
358                    // Shouldn't get called
359                    p("return null;");
360                }
361            }
362            i(-1).p("}");
363            p();
364            p("public " + source.getName() + " decode(" + JSON_VALUE_CLASS + " value) {").i(1);
365            {
366                if (classStyle == Style.RAILS) {
367                    p(JSON_OBJECT_CLASS + " object = toObjectFromWrapper(value, \"" + railsWrapperName + "\");");
368                } else if (typeInfo != null && typeInfo.include() == As.WRAPPER_ARRAY) {
369                    p(JSON_ARRAY_CLASS + " array = (" + JSON_ARRAY_CLASS + ")value;");
370                    if (!isLeaf)
371                        p("String sourceName = org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.decode(array.get(0));");
372                    p(JSON_OBJECT_CLASS + " object = toObject(array.get(1));");
373                } else {
374                    p(JSON_OBJECT_CLASS + " object = toObject(value);");
375                }
376        
377                if (!isLeaf && typeInfo != null && typeInfo.include() == As.PROPERTY) {
378                    p("String sourceName = org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.decode(object.get(" + wrap(typeInfo.property()) + "));");
379                }
380        
381                for (Subtype possibleType : possibleTypes) {
382                    // Try to find a constuctor that is annotated as creator
383                    final JConstructor creator = findCreator(possibleType.clazz);
384                    if (typeInfo != null) {
385                        if (typeInfo.include() == As.WRAPPER_OBJECT) {
386                            if (!isLeaf) {
387                                p("if(object.containsKey(\"" + possibleType.tag + "\"))");
388                                p("{");
389                            }
390                            p("object = toObjectFromWrapper(value, \"" + possibleType.tag + "\");");
391                        } else if (!isLeaf) {
392                            p("if(sourceName.equals(\"" + possibleType.tag + "\"))");
393                            p("{");
394                        }
395                    }
396        
397                    List<JField> orderedFields = null;
398                    if (creator != null) {
399                        p("// We found a creator so we use the annotated constructor");
400                        p("" + possibleType.clazz.getParameterizedQualifiedSourceName() + " rc = new " + possibleType.clazz.getParameterizedQualifiedSourceName() + "(");
401                        i(1).p("// The arguments are placed in the order they appear within the annotated constructor").i(-1);
402                        orderedFields = getOrderedFields(getFields(possibleType.clazz), creator);
403                        final JField lastField = orderedFields.get(orderedFields.size() - 1);
404                        for (final JField field : orderedFields) {
405                            branch("Processing field: " + field.getName(), new Branch<Void>() {
406                                public Void execute() throws UnableToCompleteException {
407                                    Json jsonAnnotation = field.getAnnotation(Json.class);
408                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
409                                    String jsonName = field.getName();
410                                    if (jsonAnnotation != null && jsonAnnotation.name().length() > 0) {
411                                        jsonName = jsonAnnotation.name();
412                                    }
413                                    String objectGetter = "object.get(" + wrap(jsonName) + ")";
414                                    String expression = locator.decodeExpression(field.getType(), objectGetter, style);
415        
416                                    if (field.getType().isPrimitive() == null) {
417                            i(1).p("" + (objectGetter + " == null || " + objectGetter + " instanceof com.google.gwt.json.client.JSONNull ? null : " + expression + ((field != lastField) ? ", " : ""))).i(-1);
418                                    } else {
419                                        i(1).p("" + expression + ((field != lastField) ? ", " : "")).i(-1);
420                                    }
421        
422                                    return null;
423                                }
424                            });
425                        }
426                        p(");");
427                    }
428                    
429                    if (orderedFields == null){
430                        p("" + possibleType.clazz.getParameterizedQualifiedSourceName() + " rc = new " + possibleType.clazz.getParameterizedQualifiedSourceName() + "();");
431                    }
432                    
433                    for (final JField field : getFields(possibleType.clazz)) {
434                        
435                        if (orderedFields != null && orderedFields.contains(field)){
436                            continue;
437                        }
438        
439                            final String setterName = getSetterName(field);
440        
441                            // If can ignore some fields right off the back..
442                            if (setterName == null && (field.isStatic() || field.isFinal() || field.isTransient())) {
443                                continue;
444                            }
445        
446                            branch("Processing field: " + field.getName(), new Branch<Void>() {
447                                public Void execute() throws UnableToCompleteException {
448        
449                                    // TODO: try to set the field with a setter
450                                    // or JSNI
451                                    if (setterName != null || field.isDefaultAccess() || field.isProtected() || field.isPublic()) {
452        
453                                        Json jsonAnnotation = field.getAnnotation(Json.class);
454                                        Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
455        
456                                        String name = field.getName();
457                                        String jsonName = field.getName();
458        
459                                        if (jsonAnnotation != null && jsonAnnotation.name().length() > 0) {
460                                            jsonName = jsonAnnotation.name();
461                                        }
462        
463                                        String objectGetter = "object.get(" + wrap(jsonName) + ")";
464                                        String expression = locator.decodeExpression(field.getType(), objectGetter, style);
465        
466                                        p("if(" + objectGetter + " != null) {").i(1);
467        
468                                        if (field.getType().isPrimitive() == null) {
469                                            p("if(" + objectGetter + " instanceof com.google.gwt.json.client.JSONNull) {").i(1);
470        
471                                            if (setterName != null) {
472                                                p("rc." + setterName + "(null);");
473                                            } else {
474                                                p("rc." + name + "=null;");
475                                            }
476        
477                                            i(-1).p("} else {").i(1);
478                                        }
479        
480                                        if (setterName != null) {
481                                            p("rc." + setterName + "(" + expression + ");");
482                                        } else {
483                                            p("rc." + name + "=" + expression + ";");
484                                        }
485                                        i(-1).p("}");
486        
487                                        if (field.getType().isPrimitive() == null) {
488                                            i(-1).p("}");
489                                        }
490        
491                                    } else {
492                                        error("field must not be private.");
493                                    }
494                                    return null;
495                                }
496                            });
497                    }
498                    
499                    p("return rc;");
500        
501                    if (typeInfo != null && !isLeaf) {
502                        p("}");
503                    }
504                }
505        
506                if (typeInfo != null && !isLeaf) {
507                    p("return null;");
508                }
509                i(-1).p("}");
510                p();
511            }
512        }
513    
514        private static Map<Class<?>, RestyJsonTypeIdResolver> sTypeIdResolverMap = null;
515    
516        private static Map<Class<?>, RestyJsonTypeIdResolver> getRestyResolverClassMap(GeneratorContext context, TreeLogger logger) throws UnableToCompleteException {
517            if (sTypeIdResolverMap == null) {
518                try {
519                    Map<Class<?>, RestyJsonTypeIdResolver> map = Maps.newHashMap();
520                    List<String> values = context.getPropertyOracle().getConfigurationProperty("org.fusesource.restygwt.jsontypeidresolver").getValues();
521                    for (String value : values)
522                        try {
523                            Class<?> clazz = Class.forName(value);
524                            RestyJsonTypeIdResolver resolver = (RestyJsonTypeIdResolver) clazz.newInstance();
525                            map.put(resolver.getTypeIdResolverClass(), resolver);
526                        } catch (Exception e) {
527                            logger.log(WARN, "Could not access class: " + values.get(0), e);
528                        }
529                        sTypeIdResolverMap = map;
530                } catch (BadPropertyValueException e) {
531                    logger.log(ERROR, "Could not acccess property: RestyJsonTypeIdResolver", e);
532                    throw new UnableToCompleteException();
533                }
534            }
535            return sTypeIdResolverMap;
536        }
537    
538        private List<JField> getOrderedFields(List<JField> fields, JConstructor creator) throws UnableToCompleteException {
539            List<JField> orderedFields = new ArrayList<JField>();
540            for (JParameter param : creator.getParameters()) {
541                JsonProperty prop = param.getAnnotation(JsonProperty.class);
542                if (prop != null) {
543                    for (JField field : fields) {
544                        if (field.getName().equals(prop.value())) {
545                            orderedFields.add(field);
546                        }
547                    }
548                } else {
549                    error("a constructor annotated with @JsonCreator requires that all paramaters are annotated with @JsonProperty.");
550                }
551            }
552    
553            return orderedFields;
554        }
555    
556        private JConstructor findCreator(JClassType sourceClazz) {
557            for (JConstructor constructor : sourceClazz.getConstructors()) {
558                if (constructor.getAnnotation(JsonCreator.class) != null) {
559                    return constructor;
560                }
561            }
562    
563            return null;
564        }
565    
566        /**
567         * 
568         * @param field
569         * @return the name for the setter for the specified field or null if a
570         *         setter can't be found.
571         */
572        private String getSetterName(JField field) {
573            String fieldName = field.getName();
574            fieldName = "set" + upperCaseFirstChar(fieldName);
575            JClassType type = field.getEnclosingType();
576            if (exists(type, field, fieldName, true)) {
577                return fieldName;
578            } else {
579                return null;
580            }
581        }
582    
583        /**
584         * 
585         * @param field
586         * @return the name for the getter for the specified field or null if a
587         *         getter can't be found.
588         */
589        private String getGetterName(JField field) {
590            String fieldName = field.getName();
591            JType booleanType = null;
592            try {
593                booleanType = find(Boolean.class);
594            } catch (UnableToCompleteException e) {
595                // do nothing
596            }
597            JClassType type = field.getEnclosingType();
598            if (field.getType().equals(JPrimitiveType.BOOLEAN) || field.getType().equals(booleanType)) {
599                fieldName = "is" + upperCaseFirstChar(field.getName());
600                if (exists(type, field, fieldName, false)) {
601                    return fieldName;
602                }
603                fieldName = "has" + upperCaseFirstChar(field.getName());
604                if (exists(type, field, fieldName, false)) {
605                    return fieldName;
606                }
607            }
608            fieldName = "get" + upperCaseFirstChar(field.getName());
609            if (exists(type, field, fieldName, false)) {
610                return fieldName;
611            } else {
612                return null;
613            }
614        }
615    
616        private String upperCaseFirstChar(String in) {
617            if (in.length() == 1) {
618                return in.toUpperCase();
619            } else {
620                return in.substring(0, 1).toUpperCase() + in.substring(1);
621            }
622        }
623    
624        /**
625         * checks whether a getter or setter exists on the specified type or any of
626         * its super classes excluding Object.
627         * 
628         * @param type
629         * @param field
630         * @param fieldName
631         * @param isSetter
632         * @return
633         */
634        private boolean exists(JClassType type, JField field, String fieldName, boolean isSetter) {
635            JType[] args = null;
636            if (isSetter) {
637                args = new JType[] { field.getType() };
638            } else {
639                args = new JType[] {};
640            }
641    
642            if (null != type.findMethod(fieldName, args)) {
643                return true;
644            } else {
645                try {
646                    JType objectType = find(Object.class);
647                    JClassType superType = type.getSuperclass();
648                    if (!objectType.equals(superType)) {
649                        return exists(superType, field, fieldName, isSetter);
650                    }
651                } catch (UnableToCompleteException e) {
652                    // do nothing
653                }
654            }
655            return false;
656        }
657    
658        /**
659         * Inspects the supplied type and all super classes up to but excluding
660         * Object and returns a list of all fields found in these classes.
661         * 
662         * @param type
663         * @return
664         */
665        private List<JField> getFields(JClassType type) {
666            return getFields(new ArrayList<JField>(), type);
667        }
668    
669        private List<JField> getFields(List<JField> allFields, JClassType type) {
670            JField[] fields = type.getFields();
671            for (JField field : fields) {
672                if (!field.isTransient()) {
673                    allFields.add(field);
674                }
675            }
676            try {
677                JType objectType = find(Object.class);
678                JClassType superType = type.getSuperclass();
679                if (!objectType.equals(superType)) {
680                    return getFields(allFields, superType);
681                }
682            } catch (UnableToCompleteException e) {
683                // do nothing
684            }
685            return allFields;
686        }
687    }