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 }