From 2fd3ca5d751e2566b8393ef4cb75e92ac3422ff9 Mon Sep 17 00:00:00 2001 From: Damien Date: Mon, 1 Oct 2012 12:22:08 +0200 Subject: [PATCH 01/18] Add generics support on RestTemplate methods --- .../processing/EBeanHolder.java | 21 +++++++++++++ .../processing/EBeansHolder.java | 5 ++++ .../processing/rest/GetProcessor.java | 30 ++++++++++++------- .../processing/rest/MethodProcessor.java | 5 +++- .../validation/rest/GetValidator.java | 4 +-- .../validation/rest/PostValidator.java | 4 +-- 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java index 24f9bee604..e73d0f564a 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java @@ -29,6 +29,7 @@ import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JMethod; import com.sun.codemodel.JSwitch; +import com.sun.codemodel.JType; import com.sun.codemodel.JVar; public class EBeanHolder { @@ -107,6 +108,26 @@ public JCodeModel codeModel() { return eBeansHolder.codeModel(); } + /** + * Parse the fully qualified class name and return a JClass instance. This + * method support both generics (it'll return a {@link JNarrowedClass}) and + * primitives (it'll autobox it to a {@link JClass}) + * + * @param fullyQualifiedClassName + * @return + */ + public JClass parseClass(String fullyQualifiedClassName) { + try { + JType jType = eBeansHolder.parseClass(fullyQualifiedClassName); + if (jType.isPrimitive()) { + return jType.boxify(); + } + return (JClass) jType; + } catch (ClassNotFoundException e) { + return eBeansHolder.refClass(fullyQualifiedClassName); + } + } + public JClass refClass(String fullyQualifiedClassName) { return eBeansHolder.refClass(fullyQualifiedClassName); } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java index 04675fd421..d8fabf31c7 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java @@ -28,6 +28,7 @@ import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JType; public class EBeansHolder { @@ -178,6 +179,10 @@ public JClass refClass(String fullyQualifiedClassName) { return refClass; } + public JType parseClass(String fullyQualifiedClassName) throws ClassNotFoundException { + return codeModel.parseType(fullyQualifiedClassName); + } + public JClass refClass(Class clazz) { return codeModel.ref(clazz); } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java index b7c5dd6070..d1523cc85c 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java @@ -21,6 +21,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NoType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -55,34 +56,43 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { TypeMirror returnType = executableElement.getReturnType(); - JClass generatedReturnType = null; - String returnTypeString = returnType.toString(); JClass expectedClass = null; + JClass generatedReturnClass = null; + String returnTypeString = returnType.toString(); if (returnType.getKind() != TypeKind.VOID) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - DeclaredType declaredReturnedType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnedType.getTypeArguments().get(0); + TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnType = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); + generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); + } else if (returnType.getKind() == TypeKind.DECLARED) { + TypeMirror enclosingType = declaredReturnType.getEnclosingType(); + if (enclosingType instanceof NoType) { + expectedClass = holder.parseClass(declaredReturnType.toString()); + } else { + expectedClass = holder.parseClass(enclosingType.toString()); + } + + generatedReturnClass = holder.parseClass(declaredReturnType.toString()); } else { - generatedReturnType = holder.refClass(returnTypeString); - expectedClass = generatedReturnType; + generatedReturnClass = holder.refClass(returnTypeString); + expectedClass = holder.refClass(returnTypeString); } } Get getAnnotation = element.getAnnotation(Get.class); String urlSuffix = getAnnotation.value(); - generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnType, codeModel)); + generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnClass, codeModel)); } @Override protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { - JClass expectedClass = methodHolder.getExpectedClass(); JClass generatedReturnType = methodHolder.getGeneratedReturnType(); - if (expectedClass == generatedReturnType) { + if (!generatedReturnType.fullName().startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { restCall = JExpr.invoke(restCall, "getBody"); } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java index 2c6b47184b..f79c27bf0c 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java @@ -29,8 +29,8 @@ import com.googlecode.androidannotations.annotations.rest.Accept; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.helper.RestAnnotationHelper; -import com.googlecode.androidannotations.processing.EBeanHolder; import com.googlecode.androidannotations.processing.DecoratingElementProcessor; +import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; @@ -66,6 +66,9 @@ protected void generateRestTemplateCallBlock(MethodProcessorHolder methodHolder) method = holder.restImplementationClass.method(JMod.PUBLIC, methodHolder.getGeneratedReturnType(), methodName); } method.annotate(Override.class); + if (expectedClass != generatedReturnType && !generatedReturnType.fullName().startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + method.annotate(SuppressWarnings.class).param("value", "unchecked"); + } JBlock body = method.body(); diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/GetValidator.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/GetValidator.java index b97dc9307b..f41655aaf4 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/GetValidator.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/GetValidator.java @@ -58,10 +58,8 @@ public boolean validate(Element element, AnnotationElements validatedElements) { validatorHelper.throwsOnlyRestClientException(executableElement, valid); - validatorHelper.returnTypeNotGenericUnlessResponseEntity(executableElement, valid); - validatorHelper.doesNotReturnPrimitive(executableElement, valid); - + restAnnotationHelper.urlVariableNamesExistInParametersAndHasNoOneMoreParameter(executableElement, valid); return valid.isValid(); diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/PostValidator.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/PostValidator.java index e0badca3fe..7853e4a70a 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/PostValidator.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/validation/rest/PostValidator.java @@ -57,10 +57,8 @@ public boolean validate(Element element, AnnotationElements validatedElements) { ExecutableElement executableElement = (ExecutableElement) element; validatorHelper.throwsOnlyRestClientException(executableElement, valid); - + validatorHelper.doesNotReturnPrimitive(executableElement, valid); - - validatorHelper.returnTypeNotGenericUnlessResponseEntity(executableElement, valid); restAnnotationHelper.urlVariableNamesExistInParametersAndHasOnlyOneMoreParameter(executableElement, valid); From 6aeef78a4d339edbe3a312147ab23147bca42f04 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 2 Oct 2012 22:16:29 +0200 Subject: [PATCH 02/18] Fix a ClassCastException --- .../androidannotations/processing/rest/GetProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java index d1523cc85c..8e3a6f75c5 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java @@ -61,13 +61,13 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { String returnTypeString = returnType.toString(); if (returnType.getKind() != TypeKind.VOID) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + DeclaredType declaredReturnType = (DeclaredType) returnType; TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); expectedClass = holder.refClass(typeParameter.toString()); generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); } else if (returnType.getKind() == TypeKind.DECLARED) { + DeclaredType declaredReturnType = (DeclaredType) returnType; TypeMirror enclosingType = declaredReturnType.getEnclosingType(); if (enclosingType instanceof NoType) { expectedClass = holder.parseClass(declaredReturnType.toString()); From 5af22d6aa2b4ec512e9a566556b7032905a808da Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 2 Oct 2012 23:11:49 +0200 Subject: [PATCH 03/18] Refactoring of Get and Post processors --- .../processing/rest/GetPostProcessor.java | 62 +++++++++++++++ .../processing/rest/GetProcessor.java | 41 +--------- .../processing/rest/PostProcessor.java | 77 ++++++------------- 3 files changed, 88 insertions(+), 92 deletions(-) create mode 100644 AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java new file mode 100644 index 0000000000..328a2c7eb9 --- /dev/null +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -0,0 +1,62 @@ +package com.googlecode.androidannotations.processing.rest; + +import java.lang.annotation.Annotation; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; + +import com.googlecode.androidannotations.helper.CanonicalNameConstants; +import com.googlecode.androidannotations.processing.EBeanHolder; +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JInvocation; +import com.sun.codemodel.JVar; + +public abstract class GetPostProcessor extends MethodProcessor { + + protected EBeanHolder holder; + + public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { + super(processingEnv, restImplementationHolder); + } + + @Override + public abstract Class getTarget(); + + @Override + protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { + return restCall.arg(generateHttpEntityVar(methodHolder)); + } + + @Override + protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { + JClass expectedClass = methodHolder.getExpectedClass(); + + if (expectedClass != null) { + return restCall.arg(expectedClass.dotclass()); + } else { + return restCall.arg(JExpr._null()); + } + } + + @Override + protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { + JClass generatedReturnType = methodHolder.getGeneratedReturnType(); + if (generatedReturnType == null) { + return restCall; + } + + if (!generatedReturnType.fullName().startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + restCall = JExpr.invoke(restCall, "getBody"); + } + + return restCall; + } + + @Override + protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { + return generateHttpHeadersVar(holder, body, executableElement); + } + +} diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java index 8e3a6f75c5..49e6df86e0 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java @@ -28,16 +28,10 @@ import com.googlecode.androidannotations.annotations.rest.Get; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JExpr; -import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; -public class GetProcessor extends MethodProcessor { - - private EBeanHolder holder; +public class GetProcessor extends GetPostProcessor { public GetProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { super(processingEnv, restImplementationHolder); @@ -60,6 +54,7 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { JClass generatedReturnClass = null; String returnTypeString = returnType.toString(); + // TODO: Refactoring this block... if (returnType.getKind() != TypeKind.VOID) { if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { DeclaredType declaredReturnType = (DeclaredType) returnType; @@ -88,36 +83,4 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnClass, codeModel)); } - @Override - protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { - JClass generatedReturnType = methodHolder.getGeneratedReturnType(); - - if (!generatedReturnType.fullName().startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - restCall = JExpr.invoke(restCall, "getBody"); - } - - return restCall; - } - - @Override - protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(generateHttpEntityVar(methodHolder)); - } - - @Override - protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { - JClass expectedClass = methodHolder.getExpectedClass(); - - if (expectedClass != null) { - return restCall.arg(expectedClass.dotclass()); - } else { - return restCall.arg(JExpr._null()); - } - } - - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); - } - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java index 1cfe0ae7c5..a440105511 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java @@ -21,22 +21,17 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NoType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import com.googlecode.androidannotations.annotations.rest.Post; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JExpr; -import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; -public class PostProcessor extends MethodProcessor { - - private EBeanHolder holder; +public class PostProcessor extends GetPostProcessor { public PostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { super(processingEnv, restImplementationHolder); @@ -55,66 +50,42 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { TypeMirror returnType = executableElement.getReturnType(); - JClass generatedReturnType = null; - String returnTypeString = returnType.toString(); JClass expectedClass = null; + JClass generatedReturnClass = null; + String returnTypeString = returnType.toString(); + // TODO: Refactoring this block... if (returnType.getKind() != TypeKind.VOID) { if (returnTypeString.startsWith(CanonicalNameConstants.URI)) { - DeclaredType declaredReturnedType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnedType.getTypeArguments().get(0); + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnType = holder.refClass(CanonicalNameConstants.URI); + generatedReturnClass = holder.refClass(CanonicalNameConstants.URI); } else if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - DeclaredType declaredReturnedType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnedType.getTypeArguments().get(0); + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnType = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); + generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); + } else if (returnType.getKind() == TypeKind.DECLARED) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror enclosingType = declaredReturnType.getEnclosingType(); + if (enclosingType instanceof NoType) { + expectedClass = holder.parseClass(declaredReturnType.toString()); + } else { + expectedClass = holder.parseClass(enclosingType.toString()); + } + + generatedReturnClass = holder.parseClass(declaredReturnType.toString()); } else { - generatedReturnType = holder.refClass(returnTypeString); - expectedClass = generatedReturnType; + generatedReturnClass = holder.refClass(returnTypeString); + expectedClass = holder.refClass(returnTypeString); } } Post postAnnotation = element.getAnnotation(Post.class); String urlSuffix = postAnnotation.value(); - generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnType, codeModel)); - } - - @Override - protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(generateHttpEntityVar(methodHolder)); - } - - @Override - protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { - JClass expectedClass = methodHolder.getExpectedClass(); - - if (expectedClass != null) { - restCall.arg(expectedClass.dotclass()); - } else { - restCall.arg(JExpr._null()); - } - - return restCall; - } - - @Override - protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { - JClass expectedClass = methodHolder.getExpectedClass(); - JClass generatedReturnType = methodHolder.getGeneratedReturnType(); - - if (expectedClass == generatedReturnType && expectedClass != null) { - restCall = JExpr.invoke(restCall, "getBody"); - } - - return restCall; - } - - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); + generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnClass, codeModel)); } } From ea5503b4cc08abeb28d9aa2b0d3c1d46ec4c369c Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 2 Oct 2012 23:12:16 +0200 Subject: [PATCH 04/18] Add unit tests for generics rest methods --- .../test15/rest/GenericEvent.java | 20 +++++++++++ .../test15/rest/MyService.java | 33 +++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java new file mode 100644 index 0000000000..5181db0cc0 --- /dev/null +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2010-2012 eBusiness Information, Excilys Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed To in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.googlecode.androidannotations.test15.rest; + +public class GenericEvent { + +} diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java index 7cd1f0adec..682432b5ee 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java @@ -15,6 +15,8 @@ */ package com.googlecode.androidannotations.test15.rest; +import java.util.List; +import java.util.Map; import java.util.Set; import org.springframework.http.HttpHeaders; @@ -53,8 +55,7 @@ public interface MyService { // The response can be a ResponseEntity @Get("/events/{year}/{location}") /* - * You may (or may not) declare throwing RestClientException (as a reminder, - * since it's a RuntimeException), but nothing else. + * You may (or may not) declare throwing RestClientException (as a reminder, since it's a RuntimeException), but nothing else. */ ResponseEntity getEvents2(String location, int year) throws RestClientException; @@ -64,6 +65,18 @@ public interface MyService { @Get("/events/{year}/{location}") ResponseEntity getEventsArrayOfArrays2(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + List getEventsGenericsList(String location, int year) throws RestClientException; + + @Get("/events/{year}/{location}") + Set getEventsGenericsSet(String location, int year) throws RestClientException; + + @Get("/events/{year}/{location}") + GenericEvent>> getEventsGenericsInception(String location, int year) throws RestClientException; + + @Get("/events/{year}/{location}") + Map getEventsGenericsMap(String location, int year) throws RestClientException; + // There should be max 1 parameter that is not mapped to an attribute. This // parameter will be used as the post entity. @Post("/events/") @@ -82,6 +95,22 @@ public interface MyService { @Post("/events/") ResponseEntity addEvent3(Event event); + @Post("/events/") + List addEventGenericsList(Event event); + + // TODO: Handle generics in params + // @Post("/events/") + // List addEventGenericsList(List events); + + @Post("/events/") + Set addEventGenericsSet(Event event); + + @Post("/events/") + GenericEvent>> addEventGenericsInception(Event event); + + @Post("/events/") + Map addEventGenericsMap(Event event); + /** * Output different then input */ From 8b29b74645bc98a6315a3f214c827abf75443cca Mon Sep 17 00:00:00 2001 From: Damien Date: Sun, 7 Oct 2012 15:15:07 +0200 Subject: [PATCH 05/18] Fix EBeanHolder.parseClass() to use a buffer. A partial copy of JCodeModel methods was needed because this class is final. --- .../processing/EBeanHolder.java | 19 +- .../processing/EBeansHolder.java | 191 +++++++++++++++++- 2 files changed, 185 insertions(+), 25 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java index e73d0f564a..25cfed5136 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java @@ -29,7 +29,6 @@ import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JMethod; import com.sun.codemodel.JSwitch; -import com.sun.codemodel.JType; import com.sun.codemodel.JVar; public class EBeanHolder { @@ -108,24 +107,8 @@ public JCodeModel codeModel() { return eBeansHolder.codeModel(); } - /** - * Parse the fully qualified class name and return a JClass instance. This - * method support both generics (it'll return a {@link JNarrowedClass}) and - * primitives (it'll autobox it to a {@link JClass}) - * - * @param fullyQualifiedClassName - * @return - */ public JClass parseClass(String fullyQualifiedClassName) { - try { - JType jType = eBeansHolder.parseClass(fullyQualifiedClassName); - if (jType.isPrimitive()) { - return jType.boxify(); - } - return (JClass) jType; - } catch (ClassNotFoundException e) { - return eBeansHolder.refClass(fullyQualifiedClassName); - } + return eBeansHolder.parseClass(fullyQualifiedClassName); } public JClass refClass(String fullyQualifiedClassName) { diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java index d8fabf31c7..2127a96a8a 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java @@ -20,7 +20,9 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.lang.model.element.Element; @@ -165,22 +167,82 @@ public JClass refClass(String fullyQualifiedClassName) { fullyQualifiedClassName = fullyQualifiedClassName.substring(0, fullyQualifiedClassName.length() - 2); } - JClass refClass = loadedClasses.get(fullyQualifiedClassName); + JClass refClass = uniqueRefClass(fullyQualifiedClassName); + for (int i = 0; i < arrayCounter; i++) { + refClass = refClass.array(); + } + + return refClass; + } + + /** + * Return a unique JClass reference by using {@link JCodeModel#ref(String)} + * and keeping a buffer. + * + * @param fullyQualifiedClassName + * @return + */ + private JClass uniqueRefClass(String fullyQualifiedClassName) { + JClass refClass = loadedClasses.get(fullyQualifiedClassName); if (refClass == null) { - refClass = codeModel.directClass(fullyQualifiedClassName); + refClass = codeModel.ref(fullyQualifiedClassName); loadedClasses.put(fullyQualifiedClassName, refClass); } + return refClass; + } - for (int i = 0; i < arrayCounter; i++) { - refClass = refClass.array(); - } + /** + * Parse the fully qualified class name and return a JClass instance. This + * method support both generics (it'll return a {@link JNarrowedClass}) and + * primitives (it'll autobox it to a {@link JClass}) + * + * @param fullyQualifiedClassName + * @return + */ + public JClass parseClass(String fullyQualifiedClassName) { + JClass refClass = loadedClasses.get(fullyQualifiedClassName); + if (refClass == null) { + try { + JType jType = parseType(fullyQualifiedClassName); + if (jType.isPrimitive()) { + refClass = jType.boxify(); + } + refClass = (JClass) jType; + } catch (Throwable e) { + refClass = refClass(fullyQualifiedClassName); + } + loadedClasses.put(fullyQualifiedClassName, refClass); + } return refClass; } - public JType parseClass(String fullyQualifiedClassName) throws ClassNotFoundException { - return codeModel.parseType(fullyQualifiedClassName); + /** + * Obtains a type object from a type name. + * + *

+ * This method handles primitive types, arrays, and existing {@link Class} + * es. + * + * @exception ClassNotFoundException + * If the specified type is not found. + */ + private JType parseType(String name) throws ClassNotFoundException { + // array + if (name.endsWith("[]")) { + return parseType(name.substring(0, name.length() - 2)).array(); + } + + // try primitive type + try { + return JType.parse(codeModel, name); + } catch (IllegalArgumentException e) { + ; + } + + // existing class + return new TypeNameParser(name).parseTypeName(); } public JClass refClass(Class clazz) { @@ -195,4 +257,119 @@ public Classes classes() { return classes; } + private final class TypeNameParser { + private final String s; + private int idx; + + public TypeNameParser(String s) { + this.s = s; + } + + /** + * Parses a type name token T (which can be potentially of the form + * Tr&ly;T1,T2,...>, or "? extends/super T".) + * + * @return the index of the character next to T. + */ + JClass parseTypeName() throws ClassNotFoundException { + int start = idx; + + if (s.charAt(idx) == '?') { + // wildcard + idx++; + ws(); + String head = s.substring(idx); + if (head.startsWith("extends")) { + idx += 7; + ws(); + return parseTypeName().wildcard(); + } else if (head.startsWith("super")) { + throw new UnsupportedOperationException("? super T not implemented"); + } else { + // not supported + throw new IllegalArgumentException("only extends/super can follow ?, but found " + s.substring(idx)); + } + } + + while (idx < s.length()) { + char ch = s.charAt(idx); + if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { + idx++; + } else { + break; + } + } + + JClass clazz = uniqueRefClass(s.substring(start, idx)); + + return parseSuffix(clazz); + } + + /** + * Parses additional left-associative suffixes, like type arguments and + * array specifiers. + */ + private JClass parseSuffix(JClass clazz) throws ClassNotFoundException { + if (idx == s.length()) { + return clazz; // hit EOL + } + + char ch = s.charAt(idx); + + if (ch == '<') { + return parseSuffix(parseArguments(clazz)); + } + + if (ch == '[') { + if (s.charAt(idx + 1) == ']') { + idx += 2; + return parseSuffix(clazz.array()); + } + throw new IllegalArgumentException("Expected ']' but found " + s.substring(idx + 1)); + } + + return clazz; + } + + /** + * Skips whitespaces + */ + private void ws() { + while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) { + idx++; + } + } + + /** + * Parses '<T1,T2,...,Tn>' + * + * @return the index of the character next to '>' + */ + private JClass parseArguments(JClass rawType) throws ClassNotFoundException { + if (s.charAt(idx) != '<') { + throw new IllegalArgumentException(); + } + idx++; + + List args = new ArrayList(); + + while (true) { + args.add(parseTypeName()); + if (idx == s.length()) { + throw new IllegalArgumentException("Missing '>' in " + s); + } + char ch = s.charAt(idx); + if (ch == '>') { + return rawType.narrow(args.toArray(new JClass[args.size()])); + } + + if (ch != ',') { + throw new IllegalArgumentException(s); + } + idx++; + } + + } + } + } From 434b5289278ebb6b7ad6dcf2a8439fcc29206954 Mon Sep 17 00:00:00 2001 From: Damien Date: Sun, 7 Oct 2012 16:40:13 +0200 Subject: [PATCH 06/18] Refactoring of Get and Post processors --- .../processing/rest/GetPostProcessor.java | 23 +++++++ .../processing/rest/GetProcessor.java | 56 +++++++--------- .../rest/MethodProcessorHolder.java | 8 +++ .../processing/rest/PostProcessor.java | 67 +++++++++---------- 4 files changed, 87 insertions(+), 67 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index 328a2c7eb9..3bfeaa003c 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -3,12 +3,16 @@ import java.lang.annotation.Annotation; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; +import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JExpr; import com.sun.codemodel.JInvocation; import com.sun.codemodel.JVar; @@ -24,6 +28,25 @@ public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementations @Override public abstract Class getTarget(); + @Override + public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { + this.holder = holder; + ExecutableElement executableElement = (ExecutableElement) element; + TypeMirror returnType = executableElement.getReturnType(); + String urlSuffix = retrieveUrlSuffix(element); + MethodProcessorHolder processorHolder = new MethodProcessorHolder(holder, executableElement, urlSuffix, null, null, codeModel); + + if (returnType.getKind() != TypeKind.VOID) { + retrieveReturnClass(holder, returnType, processorHolder); + } + + generateRestTemplateCallBlock(processorHolder); + } + + public abstract void retrieveReturnClass(EBeanHolder holder, TypeMirror returnType, MethodProcessorHolder processorHolder); + + public abstract String retrieveUrlSuffix(Element element); + @Override protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { return restCall.arg(generateHttpEntityVar(methodHolder)); diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java index 49e6df86e0..c11d9f27a3 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java @@ -19,7 +19,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.NoType; import javax.lang.model.type.TypeKind; @@ -29,7 +28,6 @@ import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JClass; -import com.sun.codemodel.JCodeModel; public class GetProcessor extends GetPostProcessor { @@ -43,44 +41,40 @@ public Class getTarget() { } @Override - public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { - - this.holder = holder; - ExecutableElement executableElement = (ExecutableElement) element; - - TypeMirror returnType = executableElement.getReturnType(); - + public void retrieveReturnClass(EBeanHolder holder, TypeMirror returnType, MethodProcessorHolder processorHolder) { + String returnTypeString = returnType.toString(); JClass expectedClass = null; JClass generatedReturnClass = null; - String returnTypeString = returnType.toString(); - // TODO: Refactoring this block... - if (returnType.getKind() != TypeKind.VOID) { - if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); - expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); - } else if (returnType.getKind() == TypeKind.DECLARED) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror enclosingType = declaredReturnType.getEnclosingType(); - if (enclosingType instanceof NoType) { - expectedClass = holder.parseClass(declaredReturnType.toString()); - } else { - expectedClass = holder.parseClass(enclosingType.toString()); - } + if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); + expectedClass = holder.refClass(typeParameter.toString()); + generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); - generatedReturnClass = holder.parseClass(declaredReturnType.toString()); + } else if (returnType.getKind() == TypeKind.DECLARED) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror enclosingType = declaredReturnType.getEnclosingType(); + if (enclosingType instanceof NoType) { + expectedClass = holder.parseClass(declaredReturnType.toString()); } else { - generatedReturnClass = holder.refClass(returnTypeString); - expectedClass = holder.refClass(returnTypeString); + expectedClass = holder.parseClass(enclosingType.toString()); } + generatedReturnClass = holder.parseClass(declaredReturnType.toString()); + + } else { + generatedReturnClass = holder.refClass(returnTypeString); + expectedClass = holder.refClass(returnTypeString); } - Get getAnnotation = element.getAnnotation(Get.class); - String urlSuffix = getAnnotation.value(); + processorHolder.setExpectedClass(expectedClass); + processorHolder.setGeneratedReturnType(generatedReturnClass); + } - generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnClass, codeModel)); + @Override + public String retrieveUrlSuffix(Element element) { + Get getAnnotation = element.getAnnotation(Get.class); + return getAnnotation.value(); } } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java index ef9b583556..a59a7740c7 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java @@ -58,10 +58,18 @@ public JClass getExpectedClass() { return expectedClass; } + public void setExpectedClass(JClass expectedClass) { + this.expectedClass = expectedClass; + } + public JClass getGeneratedReturnType() { return generatedReturnType; } + public void setGeneratedReturnType(JClass generatedReturnType) { + this.generatedReturnType = generatedReturnType; + } + public JCodeModel getCodeModel() { return codeModel; } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java index a440105511..07d5f0e12d 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java @@ -19,7 +19,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.NoType; import javax.lang.model.type.TypeKind; @@ -29,7 +28,6 @@ import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JClass; -import com.sun.codemodel.JCodeModel; public class PostProcessor extends GetPostProcessor { @@ -43,49 +41,46 @@ public Class getTarget() { } @Override - public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { - - this.holder = holder; - ExecutableElement executableElement = (ExecutableElement) element; - - TypeMirror returnType = executableElement.getReturnType(); - + public void retrieveReturnClass(EBeanHolder holder, TypeMirror returnType, MethodProcessorHolder processorHolder) { + String returnTypeString = returnType.toString(); JClass expectedClass = null; JClass generatedReturnClass = null; - String returnTypeString = returnType.toString(); - // TODO: Refactoring this block... - if (returnType.getKind() != TypeKind.VOID) { - if (returnTypeString.startsWith(CanonicalNameConstants.URI)) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); - expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnClass = holder.refClass(CanonicalNameConstants.URI); - } else if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); - expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); - } else if (returnType.getKind() == TypeKind.DECLARED) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror enclosingType = declaredReturnType.getEnclosingType(); - if (enclosingType instanceof NoType) { - expectedClass = holder.parseClass(declaredReturnType.toString()); - } else { - expectedClass = holder.parseClass(enclosingType.toString()); - } + if (returnTypeString.startsWith(CanonicalNameConstants.URI)) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); + expectedClass = holder.refClass(typeParameter.toString()); + generatedReturnClass = holder.refClass(CanonicalNameConstants.URI); - generatedReturnClass = holder.parseClass(declaredReturnType.toString()); + } else if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); + expectedClass = holder.refClass(typeParameter.toString()); + generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); + + } else if (returnType.getKind() == TypeKind.DECLARED) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + TypeMirror enclosingType = declaredReturnType.getEnclosingType(); + if (enclosingType instanceof NoType) { + expectedClass = holder.parseClass(declaredReturnType.toString()); } else { - generatedReturnClass = holder.refClass(returnTypeString); - expectedClass = holder.refClass(returnTypeString); + expectedClass = holder.parseClass(enclosingType.toString()); } + generatedReturnClass = holder.parseClass(declaredReturnType.toString()); + + } else { + generatedReturnClass = holder.refClass(returnTypeString); + expectedClass = holder.refClass(returnTypeString); } - Post postAnnotation = element.getAnnotation(Post.class); - String urlSuffix = postAnnotation.value(); + processorHolder.setExpectedClass(expectedClass); + processorHolder.setGeneratedReturnType(generatedReturnClass); + } - generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, expectedClass, generatedReturnClass, codeModel)); + @Override + public String retrieveUrlSuffix(Element element) { + Post getAnnotation = element.getAnnotation(Post.class); + return getAnnotation.value(); } } From 5121f836c56fc8292e51199637914b8f55752e3c Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 9 Oct 2012 11:00:47 +0200 Subject: [PATCH 07/18] Extract custom JCodeModel code into a new class --- .../processing/BufferedJCodeModel.java | 175 ++++++++++++++++++ .../processing/EBeansHolder.java | 151 +-------------- 2 files changed, 180 insertions(+), 146 deletions(-) create mode 100644 AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java new file mode 100644 index 0000000000..a3cf2acfa0 --- /dev/null +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java @@ -0,0 +1,175 @@ +package com.googlecode.androidannotations.processing; + +import java.util.ArrayList; +import java.util.List; + +import com.sun.codemodel.JClass; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JType; + +/** + * This class is a decorator of {@link JCodeModel} allowing usage of + * {@link #parseType(String)} and keeping each {@link JClass} found into the + * {@link EBeansHolder} buffer. This is useful for generics definition. + *

+ * Note : It's a hard-copy of a methods subset of {@link JCodeModel} with + * just one custom line (in {@link TypeNameParser#parseTypeName()} : + * + *

+ * JClass clazz = eBeansHolder.uniqueRefClass(s.substring(start, idx));
+ * 
+ * + * This solution was needed because JCodeModel is a final class. + */ +public class BufferedJCodeModel { + + private final EBeansHolder eBeansHolder; + private final JCodeModel codeModel; + + public BufferedJCodeModel(EBeansHolder eBeansHolder, JCodeModel codeModel) { + this.eBeansHolder = eBeansHolder; + this.codeModel = codeModel; + } + + /** + * Obtains a type object from a type name. + * + *

+ * This method handles primitive types, arrays, and existing {@link Class} + * es. + * + * @exception ClassNotFoundException + * If the specified type is not found. + */ + JType parseType(String name) throws ClassNotFoundException { + // array + if (name.endsWith("[]")) { + return parseType(name.substring(0, name.length() - 2)).array(); + } + + // try primitive type + try { + return JType.parse(codeModel, name); + } catch (IllegalArgumentException e) { + ; + } + + // existing class + return new TypeNameParser(name).parseTypeName(); + } + + private final class TypeNameParser { + private final String s; + private int idx; + + public TypeNameParser(String s) { + this.s = s; + } + + /** + * Parses a type name token T (which can be potentially of the form + * Tr&ly;T1,T2,...>, or "? extends/super T".) + * + * @return the index of the character next to T. + */ + JClass parseTypeName() throws ClassNotFoundException { + int start = idx; + + if (s.charAt(idx) == '?') { + // wildcard + idx++; + ws(); + String head = s.substring(idx); + if (head.startsWith("extends")) { + idx += 7; + ws(); + return parseTypeName().wildcard(); + } else if (head.startsWith("super")) { + throw new UnsupportedOperationException("? super T not implemented"); + } else { + // not supported + throw new IllegalArgumentException("only extends/super can follow ?, but found " + s.substring(idx)); + } + } + + while (idx < s.length()) { + char ch = s.charAt(idx); + if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { + idx++; + } else { + break; + } + } + + JClass clazz = eBeansHolder.uniqueRefClass(s.substring(start, idx)); + + return parseSuffix(clazz); + } + + /** + * Parses additional left-associative suffixes, like type arguments and + * array specifiers. + */ + private JClass parseSuffix(JClass clazz) throws ClassNotFoundException { + if (idx == s.length()) { + return clazz; // hit EOL + } + + char ch = s.charAt(idx); + + if (ch == '<') { + return parseSuffix(parseArguments(clazz)); + } + + if (ch == '[') { + if (s.charAt(idx + 1) == ']') { + idx += 2; + return parseSuffix(clazz.array()); + } + throw new IllegalArgumentException("Expected ']' but found " + s.substring(idx + 1)); + } + + return clazz; + } + + /** + * Skips whitespaces + */ + private void ws() { + while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) { + idx++; + } + } + + /** + * Parses '<T1,T2,...,Tn>' + * + * @return the index of the character next to '>' + */ + private JClass parseArguments(JClass rawType) throws ClassNotFoundException { + if (s.charAt(idx) != '<') { + throw new IllegalArgumentException(); + } + idx++; + + List args = new ArrayList(); + + while (true) { + args.add(parseTypeName()); + if (idx == s.length()) { + throw new IllegalArgumentException("Missing '>' in " + s); + } + char ch = s.charAt(idx); + if (ch == '>') { + return rawType.narrow(args.toArray(new JClass[args.size()])); + } + + if (ch != ',') { + throw new IllegalArgumentException(s); + } + idx++; + } + + } + } +} diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java index 2127a96a8a..84764151d8 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java @@ -20,9 +20,7 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.sql.SQLException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import javax.lang.model.element.Element; @@ -140,12 +138,15 @@ public class Classes { private final JCodeModel codeModel; + private final BufferedJCodeModel bufferedCodeModel; + private final Map loadedClasses = new HashMap(); private final Classes classes; public EBeansHolder(JCodeModel codeModel) { this.codeModel = codeModel; + bufferedCodeModel = new BufferedJCodeModel(this, codeModel); classes = new Classes(); } @@ -183,7 +184,7 @@ public JClass refClass(String fullyQualifiedClassName) { * @param fullyQualifiedClassName * @return */ - private JClass uniqueRefClass(String fullyQualifiedClassName) { + JClass uniqueRefClass(String fullyQualifiedClassName) { JClass refClass = loadedClasses.get(fullyQualifiedClassName); if (refClass == null) { refClass = codeModel.ref(fullyQualifiedClassName); @@ -204,7 +205,7 @@ public JClass parseClass(String fullyQualifiedClassName) { JClass refClass = loadedClasses.get(fullyQualifiedClassName); if (refClass == null) { try { - JType jType = parseType(fullyQualifiedClassName); + JType jType = bufferedCodeModel.parseType(fullyQualifiedClassName); if (jType.isPrimitive()) { refClass = jType.boxify(); } @@ -218,33 +219,6 @@ public JClass parseClass(String fullyQualifiedClassName) { return refClass; } - /** - * Obtains a type object from a type name. - * - *

- * This method handles primitive types, arrays, and existing {@link Class} - * es. - * - * @exception ClassNotFoundException - * If the specified type is not found. - */ - private JType parseType(String name) throws ClassNotFoundException { - // array - if (name.endsWith("[]")) { - return parseType(name.substring(0, name.length() - 2)).array(); - } - - // try primitive type - try { - return JType.parse(codeModel, name); - } catch (IllegalArgumentException e) { - ; - } - - // existing class - return new TypeNameParser(name).parseTypeName(); - } - public JClass refClass(Class clazz) { return codeModel.ref(clazz); } @@ -257,119 +231,4 @@ public Classes classes() { return classes; } - private final class TypeNameParser { - private final String s; - private int idx; - - public TypeNameParser(String s) { - this.s = s; - } - - /** - * Parses a type name token T (which can be potentially of the form - * Tr&ly;T1,T2,...>, or "? extends/super T".) - * - * @return the index of the character next to T. - */ - JClass parseTypeName() throws ClassNotFoundException { - int start = idx; - - if (s.charAt(idx) == '?') { - // wildcard - idx++; - ws(); - String head = s.substring(idx); - if (head.startsWith("extends")) { - idx += 7; - ws(); - return parseTypeName().wildcard(); - } else if (head.startsWith("super")) { - throw new UnsupportedOperationException("? super T not implemented"); - } else { - // not supported - throw new IllegalArgumentException("only extends/super can follow ?, but found " + s.substring(idx)); - } - } - - while (idx < s.length()) { - char ch = s.charAt(idx); - if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { - idx++; - } else { - break; - } - } - - JClass clazz = uniqueRefClass(s.substring(start, idx)); - - return parseSuffix(clazz); - } - - /** - * Parses additional left-associative suffixes, like type arguments and - * array specifiers. - */ - private JClass parseSuffix(JClass clazz) throws ClassNotFoundException { - if (idx == s.length()) { - return clazz; // hit EOL - } - - char ch = s.charAt(idx); - - if (ch == '<') { - return parseSuffix(parseArguments(clazz)); - } - - if (ch == '[') { - if (s.charAt(idx + 1) == ']') { - idx += 2; - return parseSuffix(clazz.array()); - } - throw new IllegalArgumentException("Expected ']' but found " + s.substring(idx + 1)); - } - - return clazz; - } - - /** - * Skips whitespaces - */ - private void ws() { - while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) { - idx++; - } - } - - /** - * Parses '<T1,T2,...,Tn>' - * - * @return the index of the character next to '>' - */ - private JClass parseArguments(JClass rawType) throws ClassNotFoundException { - if (s.charAt(idx) != '<') { - throw new IllegalArgumentException(); - } - idx++; - - List args = new ArrayList(); - - while (true) { - args.add(parseTypeName()); - if (idx == s.length()) { - throw new IllegalArgumentException("Missing '>' in " + s); - } - char ch = s.charAt(idx); - if (ch == '>') { - return rawType.narrow(args.toArray(new JClass[args.size()])); - } - - if (ch != ',') { - throw new IllegalArgumentException(s); - } - idx++; - } - - } - } - } From aa80b6a9bd7105c48a0ea2c9d9a15294b517e4fe Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 9 Nov 2012 16:40:54 +0100 Subject: [PATCH 08/18] Refactoring and comment rest processors --- .../AndroidAnnotationProcessor.java | 16 ++-- .../processing/rest/DeleteProcessor.java | 32 +------- .../processing/rest/GetPostProcessor.java | 27 +++---- .../processing/rest/GetProcessor.java | 3 +- .../processing/rest/HeadProcessor.java | 24 +----- .../processing/rest/MethodProcessor.java | 81 ++++++++++--------- .../processing/rest/OptionsProcessor.java | 24 +----- .../processing/rest/PostProcessor.java | 3 +- .../processing/rest/PutProcessor.java | 25 +----- .../processing/rest/RestProcessor.java | 8 +- .../test15/rest/MyService.java | 20 ++++- 11 files changed, 95 insertions(+), 168 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/AndroidAnnotationProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/AndroidAnnotationProcessor.java index 32962fd113..cc7c80d674 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/AndroidAnnotationProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/AndroidAnnotationProcessor.java @@ -508,14 +508,14 @@ private ModelProcessor buildModelProcessor(IRClass rClass, AndroidSystemServices modelProcessor.register(new TransactionalProcessor()); modelProcessor.register(new ExtraProcessor(processingEnv)); modelProcessor.register(new SystemServiceProcessor(androidSystemServices)); - RestImplementationsHolder restImplementationHolder = new RestImplementationsHolder(); - modelProcessor.register(new RestProcessor(restImplementationHolder)); - modelProcessor.register(new GetProcessor(processingEnv, restImplementationHolder)); - modelProcessor.register(new PostProcessor(processingEnv, restImplementationHolder)); - modelProcessor.register(new PutProcessor(processingEnv, restImplementationHolder)); - modelProcessor.register(new DeleteProcessor(processingEnv, restImplementationHolder)); - modelProcessor.register(new HeadProcessor(processingEnv, restImplementationHolder)); - modelProcessor.register(new OptionsProcessor(processingEnv, restImplementationHolder)); + RestImplementationsHolder restImplementationsHolder = new RestImplementationsHolder(); + modelProcessor.register(new RestProcessor(restImplementationsHolder)); + modelProcessor.register(new GetProcessor(processingEnv, restImplementationsHolder)); + modelProcessor.register(new PostProcessor(processingEnv, restImplementationsHolder)); + modelProcessor.register(new PutProcessor(processingEnv, restImplementationsHolder)); + modelProcessor.register(new DeleteProcessor(processingEnv, restImplementationsHolder)); + modelProcessor.register(new HeadProcessor(processingEnv, restImplementationsHolder)); + modelProcessor.register(new OptionsProcessor(processingEnv, restImplementationsHolder)); modelProcessor.register(new AppProcessor()); modelProcessor.register(new OptionsMenuProcessor(processingEnv, rClass)); modelProcessor.register(new OptionsItemProcessor(processingEnv, rClass)); diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/DeleteProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/DeleteProcessor.java index cad7f42dde..2d163430f7 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/DeleteProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/DeleteProcessor.java @@ -23,18 +23,12 @@ import com.googlecode.androidannotations.annotations.rest.Delete; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JExpr; -import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; public class DeleteProcessor extends MethodProcessor { - private EBeanHolder holder; - - public DeleteProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { - super(processingEnv, restImplementationHolder); + public DeleteProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { + super(processingEnv, restImplementationsHolder); } @Override @@ -45,7 +39,6 @@ public Class getTarget() { @Override public void process(Element element, JCodeModel codeModel, EBeanHolder holder) throws Exception { - this.holder = holder; ExecutableElement executableElement = (ExecutableElement) element; Delete deleteAnnotation = element.getAnnotation(Delete.class); @@ -54,25 +47,4 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) t generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, null, null, codeModel)); } - @Override - protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - } - - @Override - protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - - } - - @Override - protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall; - } - - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); - } - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index 3bfeaa003c..2b387588f5 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -1,7 +1,5 @@ package com.googlecode.androidannotations.processing.rest; -import java.lang.annotation.Annotation; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -10,40 +8,38 @@ import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JExpr; import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; public abstract class GetPostProcessor extends MethodProcessor { protected EBeanHolder holder; - public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { - super(processingEnv, restImplementationHolder); + public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { + super(processingEnv, restImplementationsHolder); } - @Override - public abstract Class getTarget(); - @Override public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { this.holder = holder; - ExecutableElement executableElement = (ExecutableElement) element; - TypeMirror returnType = executableElement.getReturnType(); + String urlSuffix = retrieveUrlSuffix(element); + + ExecutableElement executableElement = (ExecutableElement) element; MethodProcessorHolder processorHolder = new MethodProcessorHolder(holder, executableElement, urlSuffix, null, null, codeModel); + // Retrieve return type + TypeMirror returnType = executableElement.getReturnType(); if (returnType.getKind() != TypeKind.VOID) { - retrieveReturnClass(holder, returnType, processorHolder); + retrieveReturnClass(returnType, processorHolder); } generateRestTemplateCallBlock(processorHolder); } - public abstract void retrieveReturnClass(EBeanHolder holder, TypeMirror returnType, MethodProcessorHolder processorHolder); + public abstract void retrieveReturnClass(TypeMirror returnType, MethodProcessorHolder processorHolder); public abstract String retrieveUrlSuffix(Element element); @@ -77,9 +73,4 @@ protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorH return restCall; } - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); - } - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java index c11d9f27a3..b710ff66af 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java @@ -26,7 +26,6 @@ import com.googlecode.androidannotations.annotations.rest.Get; import com.googlecode.androidannotations.helper.CanonicalNameConstants; -import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JClass; public class GetProcessor extends GetPostProcessor { @@ -41,7 +40,7 @@ public Class getTarget() { } @Override - public void retrieveReturnClass(EBeanHolder holder, TypeMirror returnType, MethodProcessorHolder processorHolder) { + public void retrieveReturnClass(TypeMirror returnType, MethodProcessorHolder processorHolder) { String returnTypeString = returnType.toString(); JClass expectedClass = null; JClass generatedReturnClass = null; diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/HeadProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/HeadProcessor.java index ebc09b93b9..6a176d4738 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/HeadProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/HeadProcessor.java @@ -24,19 +24,15 @@ import com.googlecode.androidannotations.annotations.rest.Head; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JExpr; import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; public class HeadProcessor extends MethodProcessor { - private EBeanHolder holder; - - public HeadProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { - super(processingEnv, restImplementationHolder); + public HeadProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { + super(processingEnv, restImplementationsHolder); } @Override @@ -47,7 +43,6 @@ public Class getTarget() { @Override public void process(Element element, JCodeModel codeModel, EBeanHolder holder) throws Exception { - this.holder = holder; ExecutableElement executableElement = (ExecutableElement) element; TypeMirror returnType = executableElement.getReturnType(); @@ -65,19 +60,4 @@ protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorH return JExpr.invoke(restCall, "getHeaders"); } - @Override - protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - } - - @Override - protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - } - - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); - } - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java index f79c27bf0c..d8dd467fdf 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java @@ -15,17 +15,17 @@ */ package com.googlecode.androidannotations.processing.rest; -import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; +import org.apache.http.HttpEntity; + import com.googlecode.androidannotations.annotations.rest.Accept; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.helper.RestAnnotationHelper; @@ -46,17 +46,19 @@ public abstract class MethodProcessor implements DecoratingElementProcessor { protected final RestImplementationsHolder restImplementationsHolder; protected final RestAnnotationHelper restAnnotationHelper; - public MethodProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { - restImplementationsHolder = restImplementationHolder; + public MethodProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { + this.restImplementationsHolder = restImplementationsHolder; restAnnotationHelper = new RestAnnotationHelper(processingEnv, getTarget()); } protected void generateRestTemplateCallBlock(MethodProcessorHolder methodHolder) { RestImplementationHolder holder = restImplementationsHolder.getEnclosingHolder(methodHolder.getElement()); ExecutableElement executableElement = (ExecutableElement) methodHolder.getElement(); + EBeanHolder eBeanHolder = methodHolder.getHolder(); JClass expectedClass = methodHolder.getExpectedClass(); JClass generatedReturnType = methodHolder.getGeneratedReturnType(); + // Creating method signature JMethod method; String methodName = executableElement.getSimpleName().toString(); boolean methodReturnVoid = generatedReturnType == null && expectedClass == null; @@ -70,54 +72,69 @@ protected void generateRestTemplateCallBlock(MethodProcessorHolder methodHolder) method.annotate(SuppressWarnings.class).param("value", "unchecked"); } + // Keep a reference on method's body JBlock body = method.body(); + methodHolder.setBody(body); - // exchange method call + // Keep a reference on method's parameters + TreeMap methodParams = extractMethodParamsVar(eBeanHolder, method, executableElement, holder); + methodHolder.setMethodParams(methodParams); + + // RestTemplate exchange() method call JInvocation restCall = JExpr.invoke(holder.restTemplateField, "exchange"); - // concat root url + suffix + // RestTemplate exchange() 1st arg : concat root url + suffix JInvocation concatCall = JExpr.invoke(holder.rootUrlField, "concat"); - // add url param + // RestTemplate exchange() 2nd arg : add url param restCall.arg(concatCall.arg(JExpr.lit(methodHolder.getUrlSuffix()))); - EBeanHolder eBeanHolder = methodHolder.getHolder(); + // RestTemplate exchange() 3rd arg : add HttpMethod type param JClass httpMethod = eBeanHolder.refClass(CanonicalNameConstants.HTTP_METHOD); - // add method type param String restMethodInCapitalLetters = getTarget().getSimpleName().toUpperCase(); restCall.arg(httpMethod.staticRef(restMethodInCapitalLetters)); - TreeMap methodParams = (TreeMap) generateMethodParamsVar(eBeanHolder, method, executableElement, holder); - - // update method holder - methodHolder.setBody(body); - methodHolder.setMethodParams(methodParams); - - JVar hashMapVar = generateHashMapVar(methodHolder); - restCall = addHttpEntityVar(restCall, methodHolder); restCall = addResponseEntityArg(restCall, methodHolder); - boolean hasParametersInUrl = hashMapVar != null; - if (hasParametersInUrl) { + JVar hashMapVar = generateHashMapVar(methodHolder); + if (hashMapVar != null) { restCall.arg(hashMapVar); } - restCall = addResultCallMethod(restCall, methodHolder); - - insertRestCallInBody(body, restCall, methodReturnVoid); + insertRestCallInBody(body, restCall, methodHolder, methodReturnVoid); } - protected abstract JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder); + /** + * Add the {@link HttpEntity} attribute to restTemplate.exchange() method. + * By default, the value will be null (for DELETE, HEAD and + * OPTIONS method type) + */ + protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { + return restCall.arg(JExpr._null()); + } - protected abstract JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder); + /** + * Add the response type to restTemplate.exchange() method. This is used to + * bind the response into a specific Java object. + */ + protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { + return restCall.arg(JExpr._null()); + } - protected abstract JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder); + /** + * Add an extra method calls on the result of restTemplate.exchange(). By + * default, just return the result + */ + protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { + return restCall; + } - private void insertRestCallInBody(JBlock body, JInvocation restCall, boolean methodReturnVoid) { + private void insertRestCallInBody(JBlock body, JInvocation restCall, MethodProcessorHolder methodHolder, boolean methodReturnVoid) { if (methodReturnVoid) { body.add(restCall); } else { + restCall = addResultCallMethod(restCall, methodHolder); body._return(restCall); } } @@ -126,7 +143,7 @@ private JVar generateHashMapVar(MethodProcessorHolder methodHolder) { ExecutableElement element = (ExecutableElement) methodHolder.getElement(); JCodeModel codeModel = methodHolder.getCodeModel(); JBlock body = methodHolder.getBody(); - TreeMap methodParams = methodHolder.getMethodParams(); + Map methodParams = methodHolder.getMethodParams(); JVar hashMapVar = null; List urlVariables = restAnnotationHelper.extractUrlVariableNames(element); @@ -219,7 +236,7 @@ private String retrieveAcceptAnnotationValue(ExecutableElement executableElement } } - private Map generateMethodParamsVar(EBeanHolder eBeanHolder, JMethod method, ExecutableElement executableElement, RestImplementationHolder holder) { + private TreeMap extractMethodParamsVar(EBeanHolder eBeanHolder, JMethod method, ExecutableElement executableElement, RestImplementationHolder holder) { List params = executableElement.getParameters(); TreeMap methodParams = new TreeMap(); for (VariableElement parameter : params) { @@ -235,12 +252,4 @@ private Map generateMethodParamsVar(EBeanHolder eBeanHolder, JMeth return methodParams; } - protected abstract JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement); - - @Override - public abstract Class getTarget(); - - @Override - public abstract void process(Element element, JCodeModel codeModel, EBeanHolder eBeanHolder) throws Exception; - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/OptionsProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/OptionsProcessor.java index 33bf1b4f88..667e01bd07 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/OptionsProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/OptionsProcessor.java @@ -26,19 +26,15 @@ import com.googlecode.androidannotations.annotations.rest.Options; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JExpr; import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; public class OptionsProcessor extends MethodProcessor { - private EBeanHolder holder; - - public OptionsProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { - super(processingEnv, restImplementationHolder); + public OptionsProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { + super(processingEnv, restImplementationsHolder); } @Override @@ -49,7 +45,6 @@ public Class getTarget() { @Override public void process(Element element, JCodeModel codeModel, EBeanHolder holder) throws Exception { - this.holder = holder; ExecutableElement executableElement = (ExecutableElement) element; TypeMirror returnType = executableElement.getReturnType(); @@ -75,19 +70,4 @@ protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorH return restCall; } - @Override - protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - } - - @Override - protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - } - - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); - } - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java index 07d5f0e12d..38429584c5 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java @@ -26,7 +26,6 @@ import com.googlecode.androidannotations.annotations.rest.Post; import com.googlecode.androidannotations.helper.CanonicalNameConstants; -import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JClass; public class PostProcessor extends GetPostProcessor { @@ -41,7 +40,7 @@ public Class getTarget() { } @Override - public void retrieveReturnClass(EBeanHolder holder, TypeMirror returnType, MethodProcessorHolder processorHolder) { + public void retrieveReturnClass(TypeMirror returnType, MethodProcessorHolder processorHolder) { String returnTypeString = returnType.toString(); JClass expectedClass = null; JClass generatedReturnClass = null; diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PutProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PutProcessor.java index b851329dde..64a679e7e9 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PutProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PutProcessor.java @@ -23,18 +23,13 @@ import com.googlecode.androidannotations.annotations.rest.Put; import com.googlecode.androidannotations.processing.EBeanHolder; -import com.sun.codemodel.JBlock; import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JExpr; import com.sun.codemodel.JInvocation; -import com.sun.codemodel.JVar; public class PutProcessor extends MethodProcessor { - private EBeanHolder holder; - - public PutProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) { - super(processingEnv, restImplementationHolder); + public PutProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { + super(processingEnv, restImplementationsHolder); } @Override @@ -45,7 +40,6 @@ public Class getTarget() { @Override public void process(Element element, JCodeModel codeModel, EBeanHolder holder) throws Exception { - this.holder = holder; ExecutableElement executableElement = (ExecutableElement) element; Put putAnnotation = element.getAnnotation(Put.class); @@ -54,24 +48,9 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) t generateRestTemplateCallBlock(new MethodProcessorHolder(holder, executableElement, urlSuffix, null, null, codeModel)); } - @Override - protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall; - } - @Override protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { return restCall.arg(generateHttpEntityVar(methodHolder)); } - @Override - protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessorHolder methodHolder) { - return restCall.arg(JExpr._null()); - } - - @Override - protected JVar addHttpHeadersVar(JBlock body, ExecutableElement executableElement) { - return generateHttpHeadersVar(holder, body, executableElement); - } - } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/RestProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/RestProcessor.java index 1cf7aace8e..f7ee8abadc 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/RestProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/RestProcessor.java @@ -43,10 +43,10 @@ public class RestProcessor implements GeneratingElementProcessor { private static final String SPRING_REST_TEMPLATE_QUALIFIED_NAME = "org.springframework.web.client.RestTemplate"; private static final String JAVA_STRING_QUALIFIED_NAME = "java.lang.String"; - private final RestImplementationsHolder restImplementationHolder; + private final RestImplementationsHolder restImplementationsHolder; - public RestProcessor(RestImplementationsHolder restImplementationHolder) { - this.restImplementationHolder = restImplementationHolder; + public RestProcessor(RestImplementationsHolder restImplementationsHolder) { + this.restImplementationsHolder = restImplementationsHolder; } @Override @@ -58,7 +58,7 @@ public Class getTarget() { public void process(Element element, JCodeModel codeModel, EBeansHolder eBeansHolder) throws Exception { eBeansHolder.create(element, getTarget()); - RestImplementationHolder holder = restImplementationHolder.create(element); + RestImplementationHolder holder = restImplementationsHolder.create(element); TypeElement typeElement = (TypeElement) element; String interfaceName = typeElement.getQualifiedName().toString(); diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java index 682432b5ee..2c3007b5d3 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java @@ -38,6 +38,8 @@ @Rest("http://company.com/ajax/services") // if defined, the url will be added as a prefix to every request public interface MyService { + + // *** GET *** // url variables are mapped to method parameter names. @Get("/events/{year}/{location}") @@ -68,15 +70,23 @@ public interface MyService { @Get("/events/{year}/{location}") List getEventsGenericsList(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + List getEventsGenericsListArray(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") Set getEventsGenericsSet(String location, int year) throws RestClientException; @Get("/events/{year}/{location}") GenericEvent>> getEventsGenericsInception(String location, int year) throws RestClientException; - + @Get("/events/{year}/{location}") Map getEventsGenericsMap(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + void getEventsVoid(String location, int year) throws RestClientException; + + // *** POST *** + // There should be max 1 parameter that is not mapped to an attribute. This // parameter will be used as the post entity. @Post("/events/") @@ -124,16 +134,24 @@ public interface MyService { @Accept(MediaType.APPLICATION_JSON) ResponseEntity addEvent2(Event event, int year); + // *** PUT *** + @Put("/events/{id}") void updateEvent(Event event, int id); + + // *** DELETE *** // url variables are mapped to method parameter names. @Delete("/events/{id}") void removeEvent(long id); + + // *** HEAD *** @Head("/events/{year}/{location}") HttpHeaders getEventHeaders(String location, int year); + // *** OPTIONS *** + @Options("/events/{year}/{location}") Set getEventOptions(String location, int year); From 996d4d1686a8fea897262d9dc1f27cab674f780d Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 9 Nov 2012 20:55:15 +0100 Subject: [PATCH 09/18] Fix runtime mapping of generics --- .../helper/CanonicalNameConstants.java | 7 + .../processing/EBeanHolder.java | 4 + .../processing/EBeansHolder.java | 15 ++ .../processing/rest/GetPostProcessor.java | 195 +++++++++++++++++- .../processing/rest/GetProcessor.java | 37 ---- .../processing/rest/MethodProcessor.java | 8 +- .../rest/MethodProcessorHolder.java | 12 +- .../processing/rest/PostProcessor.java | 43 ---- .../test15/rest/MyService.java | 25 ++- 9 files changed, 247 insertions(+), 99 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/helper/CanonicalNameConstants.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/helper/CanonicalNameConstants.java index b0dbbc044a..b08333c48a 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/helper/CanonicalNameConstants.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/helper/CanonicalNameConstants.java @@ -17,7 +17,10 @@ import java.net.URI; import java.sql.SQLException; +import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Set; public final class CanonicalNameConstants { @@ -25,8 +28,12 @@ public final class CanonicalNameConstants { /* * Java */ + public static final String OBJECT = Object.class.getCanonicalName(); public static final String URI = URI.class.getCanonicalName(); + public static final String MAP = Map.class.getCanonicalName(); public static final String SET = Set.class.getCanonicalName(); + public static final String LIST = List.class.getCanonicalName(); + public static final String COLLECTION = Collection.class.getCanonicalName(); public static final String COLLECTIONS = Collections.class.getCanonicalName(); public static final String STRING = String.class.getCanonicalName(); public static final String CHAR_SEQUENCE = CharSequence.class.getCanonicalName(); diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java index 25cfed5136..aa4d7864c2 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java @@ -119,4 +119,8 @@ public JClass refClass(Class clazz) { return eBeansHolder.refClass(clazz); } + public JDefinedClass definedClass(String fullyQualifiedClassName) { + return eBeansHolder.definedClass(fullyQualifiedClassName); + } + } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java index 84764151d8..4a95f8b89a 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java @@ -27,7 +27,9 @@ import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.sun.codemodel.JClass; +import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JType; public class EBeansHolder { @@ -223,6 +225,19 @@ public JClass refClass(Class clazz) { return codeModel.ref(clazz); } + public JDefinedClass definedClass(String fullyQualifiedClassName) { + JDefinedClass refClass = (JDefinedClass) loadedClasses.get(fullyQualifiedClassName); + if (refClass == null) { + try { + refClass = codeModel._class(fullyQualifiedClassName); + } catch (JClassAlreadyExistsException e) { + refClass = (JDefinedClass) refClass(fullyQualifiedClassName); + } + loadedClasses.put(fullyQualifiedClassName, refClass); + } + return refClass; + } + public JCodeModel codeModel() { return codeModel; } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index 2b387588f5..7b42f1e0e7 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -1,21 +1,46 @@ package com.googlecode.androidannotations.processing.rest; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.WildcardType; import com.googlecode.androidannotations.helper.CanonicalNameConstants; import com.googlecode.androidannotations.processing.EBeanHolder; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JExpr; import com.sun.codemodel.JInvocation; +import com.sun.codemodel.JPackage; public abstract class GetPostProcessor extends MethodProcessor { + private void log(String message) { + System.out.println("GetPostProcessor INFO : " + message); + } + + private void log(int level, String message) { + System.out.print("GetPostProcessor INFO : "); + for (int i = 0; i < level; i++) { + System.out.print(" "); + } + System.out.println(message); + } + protected EBeanHolder holder; + protected JPackage restClientPackage; public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { super(processingEnv, restImplementationsHolder); @@ -24,6 +49,7 @@ public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementations @Override public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { this.holder = holder; + restClientPackage = restImplementationsHolder.getEnclosingHolder(element).restImplementationClass._package(); String urlSuffix = retrieveUrlSuffix(element); @@ -33,16 +59,177 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { // Retrieve return type TypeMirror returnType = executableElement.getReturnType(); if (returnType.getKind() != TypeKind.VOID) { - retrieveReturnClass(returnType, processorHolder); + retrieveReturnAndExpectedClasses(returnType, processorHolder); } generateRestTemplateCallBlock(processorHolder); } - public abstract void retrieveReturnClass(TypeMirror returnType, MethodProcessorHolder processorHolder); - public abstract String retrieveUrlSuffix(Element element); + /** + * Retrieve the expected and method return classes to use in generated code. + *

+ * If the annotated method return a ResponseEntity<T> then : + * + *

+	 * expectedClass = T.class, methodReturnClass = ResponseEntity<T>
+	 * 
+ * + * + * @param returnType + * @param processorHolder + */ + public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProcessorHolder processorHolder) { + String returnTypeString = returnType.toString(); + JClass expectedClass = null; + + if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + DeclaredType declaredReturnType = (DeclaredType) returnType; + if (declaredReturnType.getTypeArguments().size() > 0) { + expectedClass = resolveExpectedClass(declaredReturnType.getTypeArguments().get(0)); + } else { + expectedClass = holder.parseClass(CanonicalNameConstants.RESPONSE_ENTITY); + } + } else { + expectedClass = resolveExpectedClass(returnType); + } + + log("expectedClass = " + expectedClass.fullName() + " extends " + expectedClass._extends().fullName()); + + processorHolder.setExpectedClass(expectedClass); + processorHolder.setMethodReturnClass(holder.parseClass(returnTypeString)); + } + + /** + * Resolve the expected class for the input type according to the following + * rules : + *
    + *
  • The type is a primitive : Directly return the JClass as usual
  • + *
  • The type is NOT a generics : Directly return the JClass as usual
  • + *
  • The type is a generics and enclosing type is a class C<T> : + * Generate a subclass of C<T> and return it
  • + *
  • The type is a generics and enclosing type is an interface I<T> + * : Looking the inheritance tree, then
  • + *
      + *
    1. One of the parent is a {@link Map} : Generate a subclass of + * {@link LinkedHashMap}<T> one and return it
    2. + *
    3. One of the parent is a {@link Set} : Generate a subclass of + * {@link TreeSet}<T> one and return it
    4. + *
    5. One of the parent is a {@link Collection} : Generate a subclass of + * {@link ArrayList}<T> one and return it
    6. + *
    7. Return {@link Object} definition
    8. + *
    + *
+ * + * @param expectedType + */ + private JClass resolveExpectedClass(TypeMirror expectedType) { + log("---------"); + log("Resolving class for " + expectedType.toString()); + + // is a class or an interface + if (expectedType.getKind() == TypeKind.DECLARED) { + DeclaredType declaredType = (DeclaredType) expectedType; + List typeArguments = declaredType.getTypeArguments(); + + log(1, "typeArguments = " + typeArguments.toString()); + + // is NOT a generics + if (typeArguments.size() == 0) { + log(expectedType.toString() + " is NOT a generics"); + + return holder.parseClass(expectedType.toString()); + } + + // is a generics + log(1, expectedType.toString() + " is a generics"); + + JClass baseClass = holder.parseClass(declaredType.toString()).erasure(); + JClass decoratedExpectedClass = retrieveDecoratedExpectedClass(declaredType, baseClass); + return decoratedExpectedClass == null ? baseClass : decoratedExpectedClass; + } + + // is not a class nor an interface + log(1, expectedType.toString() + " is a primitive or an array"); + return holder.parseClass(expectedType.toString()); + } + + /** + * Recursive method used to find if one of the grand-parent of the + * enclosingJClass is {@link Map}, {@link Set} or + * {@link Collection}. + * + * @param declaredType + * @param currentClass + * @return + */ + private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass currentClass) { + log(2, "retrieveDecoratedExpectedClass(" + declaredType.toString() + ", " + currentClass.fullName() + ")"); + + // Looking for basic java.util interfaces to set a default + // implementation + String decoratedClassName = null; + if (currentClass.fullName().equals(CanonicalNameConstants.MAP)) { + decoratedClassName = LinkedHashMap.class.getCanonicalName(); + } else if (currentClass.fullName().equals(CanonicalNameConstants.SET)) { + decoratedClassName = TreeSet.class.getCanonicalName(); + } else if (currentClass.fullName().equals(CanonicalNameConstants.LIST)) { + decoratedClassName = ArrayList.class.getCanonicalName(); + } else if (currentClass.fullName().equals(CanonicalNameConstants.COLLECTION)) { + decoratedClassName = ArrayList.class.getCanonicalName(); + } + + if (decoratedClassName != null) { + log(2, "enclosingJClass " + currentClass.fullName() + " has a known parent : " + decoratedClassName); + + String decoratedClassNameSuffix = ""; + // Configure the super class of the final decorated class + JClass decoratedSuperJClass = holder.parseClass(decoratedClassName); + for (TypeMirror typeArgument : declaredType.getTypeArguments()) { + String typeArgumentName = typeArgument.toString(); + if (typeArgument instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) typeArgument; + if (wildcardType.getExtendsBound() != null) { + typeArgumentName = wildcardType.getExtendsBound().toString(); + } else if (wildcardType.getSuperBound() != null) { + typeArgumentName = wildcardType.getSuperBound().toString(); + } else { + typeArgumentName = CanonicalNameConstants.OBJECT; + } + } + JClass narrowJClass = holder.parseClass(typeArgumentName); + decoratedSuperJClass = decoratedSuperJClass.narrow(narrowJClass); + decoratedClassNameSuffix += narrowJClass.name(); + } + + // TODO: Retrieve or generate decorated classes + + String decoratedFinalClassName = currentClass.name() + "_" + decoratedClassNameSuffix; + decoratedFinalClassName = decoratedFinalClassName.replaceAll("\\[\\]", "s"); + decoratedFinalClassName = restClientPackage.name() + "." + decoratedFinalClassName; + JDefinedClass decoratedJClass = holder.definedClass(decoratedFinalClassName); + decoratedJClass._extends(decoratedSuperJClass); + + log(2, "decoratedJClass = " + decoratedJClass.fullName()); + + return decoratedJClass; + } + + // Try to find the superclass and make a recursive call to the this + // method + log(2, "Try to find a parent for " + currentClass.toString()); + JClass enclosingSuperJClass = currentClass._extends(); + if (enclosingSuperJClass != null) { + return retrieveDecoratedExpectedClass(declaredType, enclosingSuperJClass); + } + + log(2, "Falling back to " + currentClass.toString()); + + // Falling back to the current enclosingJClass if Class can't be found + return null; + } + @Override protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { return restCall.arg(generateHttpEntityVar(methodHolder)); @@ -61,7 +248,7 @@ protected JInvocation addResponseEntityArg(JInvocation restCall, MethodProcessor @Override protected JInvocation addResultCallMethod(JInvocation restCall, MethodProcessorHolder methodHolder) { - JClass generatedReturnType = methodHolder.getGeneratedReturnType(); + JClass generatedReturnType = methodHolder.getMethodReturnClass(); if (generatedReturnType == null) { return restCall; } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java index b710ff66af..1aa9315f8e 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetProcessor.java @@ -19,14 +19,8 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.NoType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import com.googlecode.androidannotations.annotations.rest.Get; -import com.googlecode.androidannotations.helper.CanonicalNameConstants; -import com.sun.codemodel.JClass; public class GetProcessor extends GetPostProcessor { @@ -39,37 +33,6 @@ public Class getTarget() { return Get.class; } - @Override - public void retrieveReturnClass(TypeMirror returnType, MethodProcessorHolder processorHolder) { - String returnTypeString = returnType.toString(); - JClass expectedClass = null; - JClass generatedReturnClass = null; - - if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); - expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); - - } else if (returnType.getKind() == TypeKind.DECLARED) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror enclosingType = declaredReturnType.getEnclosingType(); - if (enclosingType instanceof NoType) { - expectedClass = holder.parseClass(declaredReturnType.toString()); - } else { - expectedClass = holder.parseClass(enclosingType.toString()); - } - generatedReturnClass = holder.parseClass(declaredReturnType.toString()); - - } else { - generatedReturnClass = holder.refClass(returnTypeString); - expectedClass = holder.refClass(returnTypeString); - } - - processorHolder.setExpectedClass(expectedClass); - processorHolder.setGeneratedReturnType(generatedReturnClass); - } - @Override public String retrieveUrlSuffix(Element element) { Get getAnnotation = element.getAnnotation(Get.class); diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java index d8dd467fdf..9196ee9132 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessor.java @@ -56,19 +56,19 @@ protected void generateRestTemplateCallBlock(MethodProcessorHolder methodHolder) ExecutableElement executableElement = (ExecutableElement) methodHolder.getElement(); EBeanHolder eBeanHolder = methodHolder.getHolder(); JClass expectedClass = methodHolder.getExpectedClass(); - JClass generatedReturnType = methodHolder.getGeneratedReturnType(); + JClass methodReturnClass = methodHolder.getMethodReturnClass(); // Creating method signature JMethod method; String methodName = executableElement.getSimpleName().toString(); - boolean methodReturnVoid = generatedReturnType == null && expectedClass == null; + boolean methodReturnVoid = methodReturnClass == null && expectedClass == null; if (methodReturnVoid) { method = holder.restImplementationClass.method(JMod.PUBLIC, void.class, methodName); } else { - method = holder.restImplementationClass.method(JMod.PUBLIC, methodHolder.getGeneratedReturnType(), methodName); + method = holder.restImplementationClass.method(JMod.PUBLIC, methodHolder.getMethodReturnClass(), methodName); } method.annotate(Override.class); - if (expectedClass != generatedReturnType && !generatedReturnType.fullName().startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { + if (expectedClass != methodReturnClass && !methodReturnClass.fullName().startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { method.annotate(SuppressWarnings.class).param("value", "unchecked"); } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java index a59a7740c7..2f5793a2f5 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/MethodProcessorHolder.java @@ -30,7 +30,7 @@ public class MethodProcessorHolder { private Element element; private String urlSuffix; private JClass expectedClass; - private JClass generatedReturnType; + private JClass methodReturnClass; private JCodeModel codeModel; private JBlock body; @@ -42,7 +42,7 @@ public MethodProcessorHolder(EBeanHolder holder, Element element, String urlSuff this.element = element; this.urlSuffix = urlSuffix; this.expectedClass = expectedClass; - this.generatedReturnType = generatedReturnType; + this.methodReturnClass = generatedReturnType; this.codeModel = codeModel; } @@ -62,12 +62,12 @@ public void setExpectedClass(JClass expectedClass) { this.expectedClass = expectedClass; } - public JClass getGeneratedReturnType() { - return generatedReturnType; + public JClass getMethodReturnClass() { + return methodReturnClass; } - public void setGeneratedReturnType(JClass generatedReturnType) { - this.generatedReturnType = generatedReturnType; + public void setMethodReturnClass(JClass methodReturnClass) { + this.methodReturnClass = methodReturnClass; } public JCodeModel getCodeModel() { diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java index 38429584c5..29a038bb03 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/PostProcessor.java @@ -19,14 +19,8 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.NoType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import com.googlecode.androidannotations.annotations.rest.Post; -import com.googlecode.androidannotations.helper.CanonicalNameConstants; -import com.sun.codemodel.JClass; public class PostProcessor extends GetPostProcessor { @@ -39,43 +33,6 @@ public Class getTarget() { return Post.class; } - @Override - public void retrieveReturnClass(TypeMirror returnType, MethodProcessorHolder processorHolder) { - String returnTypeString = returnType.toString(); - JClass expectedClass = null; - JClass generatedReturnClass = null; - - if (returnTypeString.startsWith(CanonicalNameConstants.URI)) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); - expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnClass = holder.refClass(CanonicalNameConstants.URI); - - } else if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror typeParameter = declaredReturnType.getTypeArguments().get(0); - expectedClass = holder.refClass(typeParameter.toString()); - generatedReturnClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY).narrow(expectedClass); - - } else if (returnType.getKind() == TypeKind.DECLARED) { - DeclaredType declaredReturnType = (DeclaredType) returnType; - TypeMirror enclosingType = declaredReturnType.getEnclosingType(); - if (enclosingType instanceof NoType) { - expectedClass = holder.parseClass(declaredReturnType.toString()); - } else { - expectedClass = holder.parseClass(enclosingType.toString()); - } - generatedReturnClass = holder.parseClass(declaredReturnType.toString()); - - } else { - generatedReturnClass = holder.refClass(returnTypeString); - expectedClass = holder.refClass(returnTypeString); - } - - processorHolder.setExpectedClass(expectedClass); - processorHolder.setGeneratedReturnType(generatedReturnClass); - } - @Override public String retrieveUrlSuffix(Element element) { Post getAnnotation = element.getAnnotation(Post.class); diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java index 2c3007b5d3..1444a1d406 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java @@ -38,7 +38,7 @@ @Rest("http://company.com/ajax/services") // if defined, the url will be added as a prefix to every request public interface MyService { - + // *** GET *** // url variables are mapped to method parameter names. @@ -73,12 +73,24 @@ public interface MyService { @Get("/events/{year}/{location}") List getEventsGenericsListArray(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + List getEventsGenericsListArrayArray(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") Set getEventsGenericsSet(String location, int year) throws RestClientException; @Get("/events/{year}/{location}") - GenericEvent>> getEventsGenericsInception(String location, int year) throws RestClientException; + GenericEvent getEventsGenericString(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + GenericEvent getEventsGenericInteger(String location, int year) throws RestClientException; + + @Get("/events/{year}/{location}") + GenericEvent> getEventsGenericListEvent(String location, int year) throws RestClientException; + + @Get("/events/{year}/{location}") + GenericEvent>> getEventsGenericsInception(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") Map getEventsGenericsMap(String location, int year) throws RestClientException; @@ -86,7 +98,7 @@ public interface MyService { void getEventsVoid(String location, int year) throws RestClientException; // *** POST *** - + // There should be max 1 parameter that is not mapped to an attribute. This // parameter will be used as the post entity. @Post("/events/") @@ -105,6 +117,9 @@ public interface MyService { @Post("/events/") ResponseEntity addEvent3(Event event); + @Post("/events/") + List addEventGenericsListWildcardExtends(Event event); + @Post("/events/") List addEventGenericsList(Event event); @@ -138,13 +153,13 @@ public interface MyService { @Put("/events/{id}") void updateEvent(Event event, int id); - + // *** DELETE *** // url variables are mapped to method parameter names. @Delete("/events/{id}") void removeEvent(long id); - + // *** HEAD *** @Head("/events/{year}/{location}") From c39252661db1656cde81e62bb97de0ef8e040d8c Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 13 Nov 2012 21:35:28 +0100 Subject: [PATCH 10/18] Handle generics of generics --- .../processing/rest/GetPostProcessor.java | 22 ++++++++++++++----- .../test15/rest/MyService.java | 6 +++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index 7b42f1e0e7..f64891396a 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -183,9 +183,9 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass if (decoratedClassName != null) { log(2, "enclosingJClass " + currentClass.fullName() + " has a known parent : " + decoratedClassName); - String decoratedClassNameSuffix = ""; // Configure the super class of the final decorated class - JClass decoratedSuperJClass = holder.parseClass(decoratedClassName); + String decoratedClassNameSuffix = ""; + JClass decoratedSuperClass = holder.parseClass(decoratedClassName); for (TypeMirror typeArgument : declaredType.getTypeArguments()) { String typeArgumentName = typeArgument.toString(); if (typeArgument instanceof WildcardType) { @@ -199,8 +199,8 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass } } JClass narrowJClass = holder.parseClass(typeArgumentName); - decoratedSuperJClass = decoratedSuperJClass.narrow(narrowJClass); - decoratedClassNameSuffix += narrowJClass.name(); + decoratedSuperClass = decoratedSuperClass.narrow(narrowJClass); + decoratedClassNameSuffix += plainName(narrowJClass); } // TODO: Retrieve or generate decorated classes @@ -209,7 +209,7 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass decoratedFinalClassName = decoratedFinalClassName.replaceAll("\\[\\]", "s"); decoratedFinalClassName = restClientPackage.name() + "." + decoratedFinalClassName; JDefinedClass decoratedJClass = holder.definedClass(decoratedFinalClassName); - decoratedJClass._extends(decoratedSuperJClass); + decoratedJClass._extends(decoratedSuperClass); log(2, "decoratedJClass = " + decoratedJClass.fullName()); @@ -230,6 +230,18 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass return null; } + protected String plainName(JClass jClass) { + String plainName = jClass.erasure().name(); + List typeParameters = jClass.getTypeParameters(); + if (typeParameters.size() > 0) { + plainName += "_"; + for (JClass typeParameter : typeParameters) { + plainName += plainName(typeParameter); + } + } + return plainName; + } + @Override protected JInvocation addHttpEntityVar(JInvocation restCall, MethodProcessorHolder methodHolder) { return restCall.arg(generateHttpEntityVar(methodHolder)); diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java index 1444a1d406..2d5aafa749 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java @@ -70,6 +70,12 @@ public interface MyService { @Get("/events/{year}/{location}") List getEventsGenericsList(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + List> getEventsGenericsListListEvent(String location, int year) throws RestClientException; + + @Get("/events/{year}/{location}") + List> getEventsGenericsListListEvents(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") List getEventsGenericsListArray(String location, int year) throws RestClientException; From 74547caefa158eee1d2baed4525a014a00af7c2b Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 14 Nov 2012 01:11:06 +0100 Subject: [PATCH 11/18] Handle array of generics --- .../processing/rest/GetPostProcessor.java | 9 ++++++++- .../androidannotations/test15/rest/MyService.java | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index f64891396a..51b0387428 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -11,6 +11,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -84,6 +85,8 @@ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProces String returnTypeString = returnType.toString(); JClass expectedClass = null; + log("\n--------- " + returnTypeString.toString() + " ---------"); + if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { DeclaredType declaredReturnType = (DeclaredType) returnType; if (declaredReturnType.getTypeArguments().size() > 0) { @@ -125,7 +128,6 @@ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProces * @param expectedType */ private JClass resolveExpectedClass(TypeMirror expectedType) { - log("---------"); log("Resolving class for " + expectedType.toString()); // is a class or an interface @@ -148,6 +150,11 @@ private JClass resolveExpectedClass(TypeMirror expectedType) { JClass baseClass = holder.parseClass(declaredType.toString()).erasure(); JClass decoratedExpectedClass = retrieveDecoratedExpectedClass(declaredType, baseClass); return decoratedExpectedClass == null ? baseClass : decoratedExpectedClass; + } else if (expectedType.getKind() == TypeKind.ARRAY) { + ArrayType arrayType = (ArrayType) expectedType; + log(1, arrayType.toString() + " is an array"); + + return resolveExpectedClass(arrayType.getComponentType()).array(); } // is not a class nor an interface diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java index 2d5aafa749..2bea15ab52 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java @@ -70,6 +70,9 @@ public interface MyService { @Get("/events/{year}/{location}") List getEventsGenericsList(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") + List[] getEventsGenericsLists(String location, int year) throws RestClientException; + @Get("/events/{year}/{location}") List> getEventsGenericsListListEvent(String location, int year) throws RestClientException; From 5193249206bc3b893a768d5e3235237557136dc4 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 14 Nov 2012 11:27:00 +0100 Subject: [PATCH 12/18] Handle generics of class --- .../processing/rest/GetPostProcessor.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index 51b0387428..bbd5c226f0 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -177,14 +177,18 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass // Looking for basic java.util interfaces to set a default // implementation String decoratedClassName = null; - if (currentClass.fullName().equals(CanonicalNameConstants.MAP)) { - decoratedClassName = LinkedHashMap.class.getCanonicalName(); - } else if (currentClass.fullName().equals(CanonicalNameConstants.SET)) { - decoratedClassName = TreeSet.class.getCanonicalName(); - } else if (currentClass.fullName().equals(CanonicalNameConstants.LIST)) { - decoratedClassName = ArrayList.class.getCanonicalName(); - } else if (currentClass.fullName().equals(CanonicalNameConstants.COLLECTION)) { - decoratedClassName = ArrayList.class.getCanonicalName(); + if (currentClass.isInterface() || currentClass.isAbstract()) { + if (currentClass.fullName().equals(CanonicalNameConstants.MAP)) { + decoratedClassName = LinkedHashMap.class.getCanonicalName(); + } else if (currentClass.fullName().equals(CanonicalNameConstants.SET)) { + decoratedClassName = TreeSet.class.getCanonicalName(); + } else if (currentClass.fullName().equals(CanonicalNameConstants.LIST)) { + decoratedClassName = ArrayList.class.getCanonicalName(); + } else if (currentClass.fullName().equals(CanonicalNameConstants.COLLECTION)) { + decoratedClassName = ArrayList.class.getCanonicalName(); + } + } else { + decoratedClassName = currentClass.erasure().fullName(); } if (decoratedClassName != null) { @@ -225,13 +229,13 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass // Try to find the superclass and make a recursive call to the this // method - log(2, "Try to find a parent for " + currentClass.toString()); + log(2, "Try to find a parent for " + currentClass.fullName()); JClass enclosingSuperJClass = currentClass._extends(); if (enclosingSuperJClass != null) { return retrieveDecoratedExpectedClass(declaredType, enclosingSuperJClass); } - log(2, "Falling back to " + currentClass.toString()); + log(2, "Falling back to " + currentClass.fullName()); // Falling back to the current enclosingJClass if Class can't be found return null; From bfaf2751ba0ce04c6cbd0a126708bb8aa3aa5eb3 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 14 Nov 2012 02:53:13 +0100 Subject: [PATCH 13/18] Add integration tests for test rest methods --- .../test15/rest/MyServiceTest.java | 107 +++++++++++++++++- .../functional-test-1-5/.factorypath | 4 - .../androidannotations/test15/rest/Event.java | 55 +++++++++ .../test15/rest/GenericEvent.java | 2 +- .../test15/rest/MyService.java | 2 +- 5 files changed, 158 insertions(+), 12 deletions(-) delete mode 100644 AndroidAnnotations/functional-test-1-5/.factorypath diff --git a/AndroidAnnotations/functional-test-1-5-tests/src/test/java/com/googlecode/androidannotations/test15/rest/MyServiceTest.java b/AndroidAnnotations/functional-test-1-5-tests/src/test/java/com/googlecode/androidannotations/test15/rest/MyServiceTest.java index 0a2c8a7c84..a0e516bac2 100644 --- a/AndroidAnnotations/functional-test-1-5-tests/src/test/java/com/googlecode/androidannotations/test15/rest/MyServiceTest.java +++ b/AndroidAnnotations/functional-test-1-5-tests/src/test/java/com/googlecode/androidannotations/test15/rest/MyServiceTest.java @@ -15,37 +15,132 @@ */ package com.googlecode.androidannotations.test15.rest; +import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import java.util.List; import java.util.Map; +import org.apache.http.message.BasicHeader; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import com.googlecode.androidannotations.test15.AndroidAnnotationsTestRunner; +import com.xtremelabs.robolectric.Robolectric; @RunWith(AndroidAnnotationsTestRunner.class) public class MyServiceTest { + private MyService_ myService = new MyService_(); + + private void addPendingResponse(String jsonResponse) { + Robolectric.addPendingHttpResponse(HttpStatus.OK.value(), jsonResponse.replaceAll("'", "\""), new BasicHeader("content-type", "application/json")); + } + @Test public void can_override_root_url() { MyService_ myService = new MyService_(); - + RestTemplate restTemplate = mock(RestTemplate.class); myService.setRestTemplate(restTemplate); - myService.setRootUrl("http://newRootUrl"); - + myService.removeEvent(42); - - verify(restTemplate).exchange(startsWith("http://newRootUrl"), Mockito. any(), Mockito.> any(), Mockito.> any(), Mockito.>any()); + verify(restTemplate).exchange(startsWith("http://newRootUrl"), Mockito. any(), Mockito.> any(), Mockito.> any(), Mockito.> any()); } - + + @Test + public void getEventsArray2() { + addPendingResponse("[{'id':1,'name':'event1'},{'id':2,'name':'event2'}]"); + ResponseEntity responseEntity = myService.getEventsArray2("test", 42); + Event[] events = responseEntity.getBody(); + + Event event1 = new Event(1, "event1"); + Event event2 = new Event(2, "event2"); + + assertEquals(2, events.length); + assertEquals(event1, events[0]); + assertEquals(event2, events[1]); + } + + @Test + public void getEventsGenericsList() { + addPendingResponse("[{'id':1,'name':'event1'},{'id':2,'name':'event2'}]"); + List events = myService.getEventsGenericsList("test", 42); + + Event event1 = new Event(1, "event1"); + Event event2 = new Event(2, "event2"); + + assertEquals(2, events.size()); + assertEquals(event1, events.get(0)); + assertEquals(event2, events.get(1)); + } + + @Test + public void getEventsGenericsArrayList() { + addPendingResponse("[[{'id':1,'name':'event1'},{'id':2,'name':'event2'}],[{'id':3,'name':'event3'}]]"); + List[] events = myService.getEventsGenericsArrayList("test", 42); + + Event event1 = new Event(1, "event1"); + Event event2 = new Event(2, "event2"); + Event event3 = new Event(3, "event3"); + + assertEquals(2, events.length); + assertEquals(event1, events[0].get(0)); + assertEquals(event2, events[0].get(1)); + assertEquals(event3, events[1].get(0)); + } + + @Test + public void getEventsGenericsListListEvent() { + addPendingResponse("[[{'id':1,'name':'event1'},{'id':2,'name':'event2'}],[{'id':3,'name':'event3'}]]"); + List> events = myService.getEventsGenericsListListEvent("test", 42); + + Event event1 = new Event(1, "event1"); + Event event2 = new Event(2, "event2"); + Event event3 = new Event(3, "event3"); + + assertEquals(2, events.size()); + assertEquals(event1, events.get(0).get(0)); + assertEquals(event2, events.get(0).get(1)); + assertEquals(event3, events.get(1).get(0)); + } + + @Test + public void getEventsGenericsListListEvents() { + addPendingResponse("[[[{'id':1,'name':'event1'}],[{'id':2,'name':'event2'}]],[[{'id':3,'name':'event3'}]]]"); + List> events = myService.getEventsGenericsListListEvents("test", 42); + + Event event1 = new Event(1, "event1"); + Event event2 = new Event(2, "event2"); + Event event3 = new Event(3, "event3"); + + assertEquals(2, events.size()); + assertEquals(event1, events.get(0).get(0)[0]); + assertEquals(event2, events.get(0).get(1)[0]); + assertEquals(event3, events.get(1).get(0)[0]); + } + + @Test + public void getEventsGenericsMap() { + addPendingResponse("{'event1':{'id':1,'name':'event1'},'event2':{'id':2,'name':'event2'}}"); + Map eventsMap = myService.getEventsGenericsMap("test", 42); + + Event event1 = new Event(1, "event1"); + Event event2 = new Event(2, "event2"); + + assertEquals(2, eventsMap.size()); + assertEquals(event1, eventsMap.get("event1")); + assertEquals(event2, eventsMap.get("event2")); + } + } diff --git a/AndroidAnnotations/functional-test-1-5/.factorypath b/AndroidAnnotations/functional-test-1-5/.factorypath deleted file mode 100644 index a16617743d..0000000000 --- a/AndroidAnnotations/functional-test-1-5/.factorypath +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/Event.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/Event.java index af05d79b2a..ea2143a9ef 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/Event.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/Event.java @@ -17,4 +17,59 @@ public class Event { + private int id; + private String name; + + public Event() { + } + + public Event(int id, String name) { + this.id = id; + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Event other = (Event) obj; + if (id != other.id) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java index 5181db0cc0..c57cc09152 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/GenericEvent.java @@ -16,5 +16,5 @@ package com.googlecode.androidannotations.test15.rest; public class GenericEvent { - + T value; } diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java index 2bea15ab52..458c952c11 100644 --- a/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java +++ b/AndroidAnnotations/functional-test-1-5/src/main/java/com/googlecode/androidannotations/test15/rest/MyService.java @@ -71,7 +71,7 @@ public interface MyService { List getEventsGenericsList(String location, int year) throws RestClientException; @Get("/events/{year}/{location}") - List[] getEventsGenericsLists(String location, int year) throws RestClientException; + List[] getEventsGenericsArrayList(String location, int year) throws RestClientException; @Get("/events/{year}/{location}") List> getEventsGenericsListListEvent(String location, int year) throws RestClientException; From e7cd6894b4c3a1c2622746d58edd941b0dfaa052 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 14 Nov 2012 15:09:56 +0100 Subject: [PATCH 14/18] Remove temporary logs --- .../processing/rest/GetPostProcessor.java | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index bbd5c226f0..186018ebe6 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -28,18 +28,6 @@ public abstract class GetPostProcessor extends MethodProcessor { - private void log(String message) { - System.out.println("GetPostProcessor INFO : " + message); - } - - private void log(int level, String message) { - System.out.print("GetPostProcessor INFO : "); - for (int i = 0; i < level; i++) { - System.out.print(" "); - } - System.out.println(message); - } - protected EBeanHolder holder; protected JPackage restClientPackage; @@ -85,8 +73,6 @@ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProces String returnTypeString = returnType.toString(); JClass expectedClass = null; - log("\n--------- " + returnTypeString.toString() + " ---------"); - if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { DeclaredType declaredReturnType = (DeclaredType) returnType; if (declaredReturnType.getTypeArguments().size() > 0) { @@ -98,8 +84,6 @@ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProces expectedClass = resolveExpectedClass(returnType); } - log("expectedClass = " + expectedClass.fullName() + " extends " + expectedClass._extends().fullName()); - processorHolder.setExpectedClass(expectedClass); processorHolder.setMethodReturnClass(holder.parseClass(returnTypeString)); } @@ -128,37 +112,26 @@ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProces * @param expectedType */ private JClass resolveExpectedClass(TypeMirror expectedType) { - log("Resolving class for " + expectedType.toString()); - // is a class or an interface if (expectedType.getKind() == TypeKind.DECLARED) { DeclaredType declaredType = (DeclaredType) expectedType; List typeArguments = declaredType.getTypeArguments(); - log(1, "typeArguments = " + typeArguments.toString()); - // is NOT a generics if (typeArguments.size() == 0) { - log(expectedType.toString() + " is NOT a generics"); - return holder.parseClass(expectedType.toString()); } // is a generics - log(1, expectedType.toString() + " is a generics"); - JClass baseClass = holder.parseClass(declaredType.toString()).erasure(); JClass decoratedExpectedClass = retrieveDecoratedExpectedClass(declaredType, baseClass); return decoratedExpectedClass == null ? baseClass : decoratedExpectedClass; } else if (expectedType.getKind() == TypeKind.ARRAY) { ArrayType arrayType = (ArrayType) expectedType; - log(1, arrayType.toString() + " is an array"); - return resolveExpectedClass(arrayType.getComponentType()).array(); } // is not a class nor an interface - log(1, expectedType.toString() + " is a primitive or an array"); return holder.parseClass(expectedType.toString()); } @@ -172,8 +145,6 @@ private JClass resolveExpectedClass(TypeMirror expectedType) { * @return */ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass currentClass) { - log(2, "retrieveDecoratedExpectedClass(" + declaredType.toString() + ", " + currentClass.fullName() + ")"); - // Looking for basic java.util interfaces to set a default // implementation String decoratedClassName = null; @@ -192,8 +163,6 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass } if (decoratedClassName != null) { - log(2, "enclosingJClass " + currentClass.fullName() + " has a known parent : " + decoratedClassName); - // Configure the super class of the final decorated class String decoratedClassNameSuffix = ""; JClass decoratedSuperClass = holder.parseClass(decoratedClassName); @@ -214,29 +183,22 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass decoratedClassNameSuffix += plainName(narrowJClass); } - // TODO: Retrieve or generate decorated classes - String decoratedFinalClassName = currentClass.name() + "_" + decoratedClassNameSuffix; decoratedFinalClassName = decoratedFinalClassName.replaceAll("\\[\\]", "s"); decoratedFinalClassName = restClientPackage.name() + "." + decoratedFinalClassName; JDefinedClass decoratedJClass = holder.definedClass(decoratedFinalClassName); decoratedJClass._extends(decoratedSuperClass); - log(2, "decoratedJClass = " + decoratedJClass.fullName()); - return decoratedJClass; } // Try to find the superclass and make a recursive call to the this // method - log(2, "Try to find a parent for " + currentClass.fullName()); JClass enclosingSuperJClass = currentClass._extends(); if (enclosingSuperJClass != null) { return retrieveDecoratedExpectedClass(declaredType, enclosingSuperJClass); } - log(2, "Falling back to " + currentClass.fullName()); - // Falling back to the current enclosingJClass if Class can't be found return null; } From 5197bfde13ada0d6a7fe9bd133fc75814c77df68 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 21 Nov 2012 15:36:27 +0100 Subject: [PATCH 15/18] Fix EBeansHolder.refClass method to handle generics. Fix rest-template generics support --- .../processing/BufferedJCodeModel.java | 175 ----------------- .../processing/EBeanHolder.java | 4 - .../processing/EBeansHolder.java | 178 +++++++++++++----- .../processing/rest/GetPostProcessor.java | 66 ++++--- .../processing/EBeansHolderTests.java | 50 +++++ 5 files changed, 221 insertions(+), 252 deletions(-) delete mode 100644 AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java create mode 100644 AndroidAnnotations/androidannotations/src/test/java/com/googlecode/androidannotations/processing/EBeansHolderTests.java diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java deleted file mode 100644 index a3cf2acfa0..0000000000 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/BufferedJCodeModel.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.googlecode.androidannotations.processing; - -import java.util.ArrayList; -import java.util.List; - -import com.sun.codemodel.JClass; -import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JType; - -/** - * This class is a decorator of {@link JCodeModel} allowing usage of - * {@link #parseType(String)} and keeping each {@link JClass} found into the - * {@link EBeansHolder} buffer. This is useful for generics definition. - *

- * Note : It's a hard-copy of a methods subset of {@link JCodeModel} with - * just one custom line (in {@link TypeNameParser#parseTypeName()} : - * - *

- * JClass clazz = eBeansHolder.uniqueRefClass(s.substring(start, idx));
- * 
- * - * This solution was needed because JCodeModel is a final class. - */ -public class BufferedJCodeModel { - - private final EBeansHolder eBeansHolder; - private final JCodeModel codeModel; - - public BufferedJCodeModel(EBeansHolder eBeansHolder, JCodeModel codeModel) { - this.eBeansHolder = eBeansHolder; - this.codeModel = codeModel; - } - - /** - * Obtains a type object from a type name. - * - *

- * This method handles primitive types, arrays, and existing {@link Class} - * es. - * - * @exception ClassNotFoundException - * If the specified type is not found. - */ - JType parseType(String name) throws ClassNotFoundException { - // array - if (name.endsWith("[]")) { - return parseType(name.substring(0, name.length() - 2)).array(); - } - - // try primitive type - try { - return JType.parse(codeModel, name); - } catch (IllegalArgumentException e) { - ; - } - - // existing class - return new TypeNameParser(name).parseTypeName(); - } - - private final class TypeNameParser { - private final String s; - private int idx; - - public TypeNameParser(String s) { - this.s = s; - } - - /** - * Parses a type name token T (which can be potentially of the form - * Tr&ly;T1,T2,...>, or "? extends/super T".) - * - * @return the index of the character next to T. - */ - JClass parseTypeName() throws ClassNotFoundException { - int start = idx; - - if (s.charAt(idx) == '?') { - // wildcard - idx++; - ws(); - String head = s.substring(idx); - if (head.startsWith("extends")) { - idx += 7; - ws(); - return parseTypeName().wildcard(); - } else if (head.startsWith("super")) { - throw new UnsupportedOperationException("? super T not implemented"); - } else { - // not supported - throw new IllegalArgumentException("only extends/super can follow ?, but found " + s.substring(idx)); - } - } - - while (idx < s.length()) { - char ch = s.charAt(idx); - if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { - idx++; - } else { - break; - } - } - - JClass clazz = eBeansHolder.uniqueRefClass(s.substring(start, idx)); - - return parseSuffix(clazz); - } - - /** - * Parses additional left-associative suffixes, like type arguments and - * array specifiers. - */ - private JClass parseSuffix(JClass clazz) throws ClassNotFoundException { - if (idx == s.length()) { - return clazz; // hit EOL - } - - char ch = s.charAt(idx); - - if (ch == '<') { - return parseSuffix(parseArguments(clazz)); - } - - if (ch == '[') { - if (s.charAt(idx + 1) == ']') { - idx += 2; - return parseSuffix(clazz.array()); - } - throw new IllegalArgumentException("Expected ']' but found " + s.substring(idx + 1)); - } - - return clazz; - } - - /** - * Skips whitespaces - */ - private void ws() { - while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) { - idx++; - } - } - - /** - * Parses '<T1,T2,...,Tn>' - * - * @return the index of the character next to '>' - */ - private JClass parseArguments(JClass rawType) throws ClassNotFoundException { - if (s.charAt(idx) != '<') { - throw new IllegalArgumentException(); - } - idx++; - - List args = new ArrayList(); - - while (true) { - args.add(parseTypeName()); - if (idx == s.length()) { - throw new IllegalArgumentException("Missing '>' in " + s); - } - char ch = s.charAt(idx); - if (ch == '>') { - return rawType.narrow(args.toArray(new JClass[args.size()])); - } - - if (ch != ',') { - throw new IllegalArgumentException(s); - } - idx++; - } - - } - } -} diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java index aa4d7864c2..b5a2117598 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeanHolder.java @@ -107,10 +107,6 @@ public JCodeModel codeModel() { return eBeansHolder.codeModel(); } - public JClass parseClass(String fullyQualifiedClassName) { - return eBeansHolder.parseClass(fullyQualifiedClassName); - } - public JClass refClass(String fullyQualifiedClassName) { return eBeansHolder.refClass(fullyQualifiedClassName); } diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java index 4a95f8b89a..93490c4274 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/EBeansHolder.java @@ -20,7 +20,9 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.lang.model.element.Element; @@ -30,7 +32,6 @@ import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDefinedClass; -import com.sun.codemodel.JType; public class EBeansHolder { @@ -140,15 +141,12 @@ public class Classes { private final JCodeModel codeModel; - private final BufferedJCodeModel bufferedCodeModel; - private final Map loadedClasses = new HashMap(); private final Classes classes; public EBeansHolder(JCodeModel codeModel) { this.codeModel = codeModel; - bufferedCodeModel = new BufferedJCodeModel(this, codeModel); classes = new Classes(); } @@ -162,20 +160,29 @@ public EBeanHolder getEBeanHolder(Element element) { return eBeanHolders.get(element); } - public JClass refClass(String fullyQualifiedClassName) { + public JClass refClass(Class clazz) { + return codeModel.ref(clazz); + } - int arrayCounter = 0; - while (fullyQualifiedClassName.endsWith("[]")) { - arrayCounter++; - fullyQualifiedClassName = fullyQualifiedClassName.substring(0, fullyQualifiedClassName.length() - 2); + public JClass refClass(String fullyQualifiedClassName) { + JClass refClass = loadedClasses.get(fullyQualifiedClassName); + if (refClass == null) { + refClass = new TypeNameParser(fullyQualifiedClassName).parseTypeName(); + loadedClasses.put(fullyQualifiedClassName, refClass); } + return refClass; + } - JClass refClass = uniqueRefClass(fullyQualifiedClassName); - - for (int i = 0; i < arrayCounter; i++) { - refClass = refClass.array(); + public JDefinedClass definedClass(String fullyQualifiedClassName) { + JDefinedClass refClass = (JDefinedClass) loadedClasses.get(fullyQualifiedClassName); + if (refClass == null) { + try { + refClass = codeModel._class(fullyQualifiedClassName); + } catch (JClassAlreadyExistsException e) { + refClass = (JDefinedClass) refClass(fullyQualifiedClassName); + } + loadedClasses.put(fullyQualifiedClassName, refClass); } - return refClass; } @@ -186,56 +193,129 @@ public JClass refClass(String fullyQualifiedClassName) { * @param fullyQualifiedClassName * @return */ - JClass uniqueRefClass(String fullyQualifiedClassName) { + JClass uniqueClass(String fullyQualifiedClassName) { JClass refClass = loadedClasses.get(fullyQualifiedClassName); if (refClass == null) { - refClass = codeModel.ref(fullyQualifiedClassName); + refClass = codeModel.directClass(fullyQualifiedClassName); loadedClasses.put(fullyQualifiedClassName, refClass); } return refClass; } - /** - * Parse the fully qualified class name and return a JClass instance. This - * method support both generics (it'll return a {@link JNarrowedClass}) and - * primitives (it'll autobox it to a {@link JClass}) - * - * @param fullyQualifiedClassName - * @return - */ - public JClass parseClass(String fullyQualifiedClassName) { - JClass refClass = loadedClasses.get(fullyQualifiedClassName); - if (refClass == null) { - try { - JType jType = bufferedCodeModel.parseType(fullyQualifiedClassName); - if (jType.isPrimitive()) { - refClass = jType.boxify(); + private final class TypeNameParser { + private final String s; + private int idx; + + public TypeNameParser(String s) { + this.s = s; + } + + /** + * Parses a type name token T (which can be potentially of the form + * Tr&ly;T1,T2,...>, or "? extends/super T".) + * + * @return the index of the character next to T. + */ + JClass parseTypeName() { + int start = idx; + + if (s.charAt(idx) == '?') { + // wildcard + idx++; + ws(); + String head = s.substring(idx); + if (head.startsWith("extends")) { + idx += 7; + ws(); + return parseTypeName().wildcard(); + } else if (head.startsWith("super")) { + throw new UnsupportedOperationException("? super T not implemented"); + } else { + // not supported + throw new IllegalArgumentException("only extends/super can follow ?, but found " + s.substring(idx)); } - refClass = (JClass) jType; - } catch (Throwable e) { - refClass = refClass(fullyQualifiedClassName); } - loadedClasses.put(fullyQualifiedClassName, refClass); + while (idx < s.length()) { + char ch = s.charAt(idx); + if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { + idx++; + } else { + break; + } + } + + JClass clazz = uniqueClass(s.substring(start, idx)); + + return parseSuffix(clazz); } - return refClass; - } - public JClass refClass(Class clazz) { - return codeModel.ref(clazz); - } + /** + * Parses additional left-associative suffixes, like type arguments and + * array specifiers. + */ + private JClass parseSuffix(JClass clazz) { + if (idx == s.length()) { + return clazz; // hit EOL + } - public JDefinedClass definedClass(String fullyQualifiedClassName) { - JDefinedClass refClass = (JDefinedClass) loadedClasses.get(fullyQualifiedClassName); - if (refClass == null) { - try { - refClass = codeModel._class(fullyQualifiedClassName); - } catch (JClassAlreadyExistsException e) { - refClass = (JDefinedClass) refClass(fullyQualifiedClassName); + char ch = s.charAt(idx); + + if (ch == '<') { + return parseSuffix(parseArguments(clazz)); } - loadedClasses.put(fullyQualifiedClassName, refClass); + + if (ch == '[') { + if (s.charAt(idx + 1) == ']') { + idx += 2; + return parseSuffix(clazz.array()); + } + throw new IllegalArgumentException("Expected ']' but found " + s.substring(idx + 1)); + } + + return clazz; + } + + /** + * Skips whitespaces + */ + private void ws() { + while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) { + idx++; + } + } + + /** + * Parses '<T1,T2,...,Tn>' + * + * @return the index of the character next to '>' + */ + private JClass parseArguments(JClass rawType) { + if (s.charAt(idx) != '<') { + throw new IllegalArgumentException(); + } + idx++; + + List args = new ArrayList(); + + while (true) { + args.add(parseTypeName()); + if (idx == s.length()) { + throw new IllegalArgumentException("Missing '>' in " + s); + } + char ch = s.charAt(idx); + if (ch == '>') { + idx++; + return rawType.narrow(args.toArray(new JClass[args.size()])); + } + + if (ch != ',') { + throw new IllegalArgumentException(s); + } + idx++; + } + } - return refClass; } public JCodeModel codeModel() { diff --git a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java index 186018ebe6..bc0e6c5f34 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/com/googlecode/androidannotations/processing/rest/GetPostProcessor.java @@ -10,7 +10,9 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; @@ -29,6 +31,10 @@ public abstract class GetPostProcessor extends MethodProcessor { protected EBeanHolder holder; + + /** + * Will be use to generate specific classes + */ protected JPackage restClientPackage; public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { @@ -71,21 +77,23 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) { */ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProcessorHolder processorHolder) { String returnTypeString = returnType.toString(); + JClass expectedClass = null; + JClass returnClass = holder.refClass(returnTypeString); if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { DeclaredType declaredReturnType = (DeclaredType) returnType; if (declaredReturnType.getTypeArguments().size() > 0) { expectedClass = resolveExpectedClass(declaredReturnType.getTypeArguments().get(0)); } else { - expectedClass = holder.parseClass(CanonicalNameConstants.RESPONSE_ENTITY); + expectedClass = holder.refClass(CanonicalNameConstants.RESPONSE_ENTITY); } } else { expectedClass = resolveExpectedClass(returnType); } processorHolder.setExpectedClass(expectedClass); - processorHolder.setMethodReturnClass(holder.parseClass(returnTypeString)); + processorHolder.setMethodReturnClass(returnClass); } /** @@ -115,24 +123,30 @@ private JClass resolveExpectedClass(TypeMirror expectedType) { // is a class or an interface if (expectedType.getKind() == TypeKind.DECLARED) { DeclaredType declaredType = (DeclaredType) expectedType; + List typeArguments = declaredType.getTypeArguments(); - // is NOT a generics - if (typeArguments.size() == 0) { - return holder.parseClass(expectedType.toString()); + // is NOT a generics, return directly + if (typeArguments.isEmpty()) { + return holder.refClass(declaredType.toString()); } - // is a generics - JClass baseClass = holder.parseClass(declaredType.toString()).erasure(); - JClass decoratedExpectedClass = retrieveDecoratedExpectedClass(declaredType, baseClass); - return decoratedExpectedClass == null ? baseClass : decoratedExpectedClass; + // is a generics, must generate a new super class + TypeElement declaredElement = (TypeElement) declaredType.asElement(); + + JClass baseClass = holder.refClass(declaredType.toString()).erasure(); + JClass decoratedExpectedClass = retrieveDecoratedExpectedClass(declaredType, declaredElement); + if (decoratedExpectedClass == null) { + decoratedExpectedClass = baseClass; + } + return decoratedExpectedClass; } else if (expectedType.getKind() == TypeKind.ARRAY) { ArrayType arrayType = (ArrayType) expectedType; return resolveExpectedClass(arrayType.getComponentType()).array(); } - // is not a class nor an interface - return holder.parseClass(expectedType.toString()); + // is not a class nor an interface, return directly + return holder.refClass(expectedType.toString()); } /** @@ -144,28 +158,31 @@ private JClass resolveExpectedClass(TypeMirror expectedType) { * @param currentClass * @return */ - private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass currentClass) { + private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, TypeElement typeElement) { + String classTypeBaseName = typeElement.toString(); + // Looking for basic java.util interfaces to set a default // implementation String decoratedClassName = null; - if (currentClass.isInterface() || currentClass.isAbstract()) { - if (currentClass.fullName().equals(CanonicalNameConstants.MAP)) { + + if (typeElement.getKind() == ElementKind.INTERFACE) { + if (classTypeBaseName.equals(CanonicalNameConstants.MAP)) { decoratedClassName = LinkedHashMap.class.getCanonicalName(); - } else if (currentClass.fullName().equals(CanonicalNameConstants.SET)) { + } else if (classTypeBaseName.equals(CanonicalNameConstants.SET)) { decoratedClassName = TreeSet.class.getCanonicalName(); - } else if (currentClass.fullName().equals(CanonicalNameConstants.LIST)) { + } else if (classTypeBaseName.equals(CanonicalNameConstants.LIST)) { decoratedClassName = ArrayList.class.getCanonicalName(); - } else if (currentClass.fullName().equals(CanonicalNameConstants.COLLECTION)) { + } else if (classTypeBaseName.equals(CanonicalNameConstants.COLLECTION)) { decoratedClassName = ArrayList.class.getCanonicalName(); } } else { - decoratedClassName = currentClass.erasure().fullName(); + decoratedClassName = typeElement.getQualifiedName().toString(); } if (decoratedClassName != null) { // Configure the super class of the final decorated class String decoratedClassNameSuffix = ""; - JClass decoratedSuperClass = holder.parseClass(decoratedClassName); + JClass decoratedSuperClass = holder.refClass(decoratedClassName); for (TypeMirror typeArgument : declaredType.getTypeArguments()) { String typeArgumentName = typeArgument.toString(); if (typeArgument instanceof WildcardType) { @@ -178,12 +195,12 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass typeArgumentName = CanonicalNameConstants.OBJECT; } } - JClass narrowJClass = holder.parseClass(typeArgumentName); + JClass narrowJClass = holder.refClass(typeArgumentName); decoratedSuperClass = decoratedSuperClass.narrow(narrowJClass); decoratedClassNameSuffix += plainName(narrowJClass); } - String decoratedFinalClassName = currentClass.name() + "_" + decoratedClassNameSuffix; + String decoratedFinalClassName = classTypeBaseName + "_" + decoratedClassNameSuffix; decoratedFinalClassName = decoratedFinalClassName.replaceAll("\\[\\]", "s"); decoratedFinalClassName = restClientPackage.name() + "." + decoratedFinalClassName; JDefinedClass decoratedJClass = holder.definedClass(decoratedFinalClassName); @@ -194,9 +211,10 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, JClass // Try to find the superclass and make a recursive call to the this // method - JClass enclosingSuperJClass = currentClass._extends(); - if (enclosingSuperJClass != null) { - return retrieveDecoratedExpectedClass(declaredType, enclosingSuperJClass); + TypeMirror enclosingSuperJClass = typeElement.getSuperclass(); + if (enclosingSuperJClass != null && enclosingSuperJClass.getKind() == TypeKind.DECLARED) { + DeclaredType declaredEnclosingSuperJClass = (DeclaredType) enclosingSuperJClass; + return retrieveDecoratedExpectedClass(declaredType, (TypeElement) declaredEnclosingSuperJClass.asElement()); } // Falling back to the current enclosingJClass if Class can't be found diff --git a/AndroidAnnotations/androidannotations/src/test/java/com/googlecode/androidannotations/processing/EBeansHolderTests.java b/AndroidAnnotations/androidannotations/src/test/java/com/googlecode/androidannotations/processing/EBeansHolderTests.java new file mode 100644 index 0000000000..9359ddb0bc --- /dev/null +++ b/AndroidAnnotations/androidannotations/src/test/java/com/googlecode/androidannotations/processing/EBeansHolderTests.java @@ -0,0 +1,50 @@ +package com.googlecode.androidannotations.processing; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.sun.codemodel.JClass; +import com.sun.codemodel.JCodeModel; + +public class EBeansHolderTests { + + private EBeansHolder eBeansHolder; + + public EBeansHolderTests() { + eBeansHolder = new EBeansHolder(new JCodeModel()); + } + + private void checkForFullyQualifiedClassName(String fullyQualifiedClassName) { + JClass refClass = eBeansHolder.refClass(fullyQualifiedClassName); + System.out.println(refClass.fullName()); + Assert.assertEquals(fullyQualifiedClassName, refClass.fullName()); + } + + @Test + public void testRefClass_generics_one_arg() { + checkForFullyQualifiedClassName("java.util.List"); + checkForFullyQualifiedClassName("java.util.List[]"); + checkForFullyQualifiedClassName("java.util.List[][]"); + checkForFullyQualifiedClassName("java.util.List"); + checkForFullyQualifiedClassName("java.util.List[]"); + } + + @Test + public void testRefClass_generics_multiple_args() { + checkForFullyQualifiedClassName("java.util.Map"); + checkForFullyQualifiedClassName("java.util.Map[]"); + checkForFullyQualifiedClassName("java.util.Map[][]"); + checkForFullyQualifiedClassName("java.util.Map"); + checkForFullyQualifiedClassName("java.util.Map"); + checkForFullyQualifiedClassName("java.util.Map[]"); + } + + @Test + public void testRefClass_generics_inner_args() { + checkForFullyQualifiedClassName("java.util.Map,java.lang.Integer>"); + checkForFullyQualifiedClassName("java.util.Map[],java.lang.Integer>"); + checkForFullyQualifiedClassName("java.util.Map[],java.lang.Integer>[]"); + } + +} From f68ae5babfe9a44170933a0d2935b921a356fb38 Mon Sep 17 00:00:00 2001 From: Damien Date: Sun, 25 Nov 2012 22:09:39 +0100 Subject: [PATCH 16/18] Handle wildcard on EBeansHolder.refClass(). TypeVar throws exception --- .../processing/EBeansHolder.java | 61 ++++++++++++------- .../processing/EBeansHolderTests.java | 40 +++++++++++- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java index 5b037093ad..9957776a03 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java @@ -28,6 +28,7 @@ import javax.lang.model.element.Element; import org.androidannotations.helper.CanonicalNameConstants; + import com.sun.codemodel.JClass; import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JCodeModel; @@ -182,8 +183,12 @@ public JClass refClass(Class clazz) { public JClass refClass(String fullyQualifiedClassName) { JClass refClass = loadedClasses.get(fullyQualifiedClassName); if (refClass == null) { - refClass = new TypeNameParser(fullyQualifiedClassName).parseTypeName(); - loadedClasses.put(fullyQualifiedClassName, refClass); + if (fullyQualifiedClassName.endsWith("[]")) { + return refClass(fullyQualifiedClassName.substring(0, fullyQualifiedClassName.length() - 2)).array(); + } else { + refClass = new TypeNameParser(fullyQualifiedClassName).parseTypeName(); + loadedClasses.put(fullyQualifiedClassName, refClass); + } } return refClass; } @@ -233,35 +238,44 @@ public TypeNameParser(String s) { */ JClass parseTypeName() { int start = idx; + String typeName = null; + // wildcard if (s.charAt(idx) == '?') { - // wildcard idx++; ws(); - String head = s.substring(idx); - if (head.startsWith("extends")) { - idx += 7; - ws(); - return parseTypeName().wildcard(); - } else if (head.startsWith("super")) { - throw new UnsupportedOperationException("? super T not implemented"); - } else { - // not supported - throw new IllegalArgumentException("only extends/super can follow ?, but found " + s.substring(idx)); - } - } - while (idx < s.length()) { + // Handle simple '?' char ch = s.charAt(idx); - if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { - idx++; - } else { - break; + if (ch == '>' || ch == ',') { + return refClass(Object.class).wildcard(); } + } else { + while (idx < s.length()) { + char ch = s.charAt(idx); + if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { + idx++; + } else { + break; + } + } + typeName = s.substring(start, idx); } - JClass clazz = uniqueClass(s.substring(start, idx)); + ws(); + String head = s.substring(idx); + if (head.startsWith("extends")) { + idx += 7; + ws(); + if (typeName != null) { + throw new UnsupportedOperationException("T extends MyObject not implemented (" + s + ")"); + } + return parseTypeName().wildcard(); + } else if (head.startsWith("super")) { + throw new UnsupportedOperationException("? super MyObject not implemented (" + s + ")"); + } + JClass clazz = uniqueClass(typeName); return parseSuffix(clazz); } @@ -295,7 +309,7 @@ private JClass parseSuffix(JClass clazz) { * Skips whitespaces */ private void ws() { - while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) { + while (idx < s.length() && Character.isWhitespace(s.charAt(idx))) { idx++; } } @@ -310,6 +324,7 @@ private JClass parseArguments(JClass rawType) { throw new IllegalArgumentException(); } idx++; + ws(); List args = new ArrayList(); @@ -321,6 +336,7 @@ private JClass parseArguments(JClass rawType) { char ch = s.charAt(idx); if (ch == '>') { idx++; + ws(); return rawType.narrow(args.toArray(new JClass[args.size()])); } @@ -328,6 +344,7 @@ private JClass parseArguments(JClass rawType) { throw new IllegalArgumentException(s); } idx++; + ws(); } } diff --git a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java index 4274eac2f8..84e150dcd7 100644 --- a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java +++ b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java @@ -16,13 +16,22 @@ public EBeansHolderTests() { } private void checkForFullyQualifiedClassName(String fullyQualifiedClassName) { + checkForFullyQualifiedClassName(fullyQualifiedClassName, fullyQualifiedClassName); + } + + private void checkForFullyQualifiedClassName(String fullyQualifiedClassName, String expetedClassName) { JClass refClass = eBeansHolder.refClass(fullyQualifiedClassName); - System.out.println(refClass.fullName()); - Assert.assertEquals(fullyQualifiedClassName, refClass.fullName()); + Assert.assertEquals(expetedClassName, refClass.fullName()); + } + + @Test + public void testRefClass_primitive() { + checkForFullyQualifiedClassName("int"); } @Test public void testRefClass_generics_one_arg() { + checkForFullyQualifiedClassName("java.util.List"); checkForFullyQualifiedClassName("java.util.List"); checkForFullyQualifiedClassName("java.util.List[]"); checkForFullyQualifiedClassName("java.util.List[][]"); @@ -38,6 +47,9 @@ public void testRefClass_generics_multiple_args() { checkForFullyQualifiedClassName("java.util.Map"); checkForFullyQualifiedClassName("java.util.Map"); checkForFullyQualifiedClassName("java.util.Map[]"); + + checkForFullyQualifiedClassName("java.util.Map", "java.util.Map"); + checkForFullyQualifiedClassName("java.util.Map[]", "java.util.Map[]"); } @Test @@ -45,6 +57,30 @@ public void testRefClass_generics_inner_args() { checkForFullyQualifiedClassName("java.util.Map,java.lang.Integer>"); checkForFullyQualifiedClassName("java.util.Map[],java.lang.Integer>"); checkForFullyQualifiedClassName("java.util.Map[],java.lang.Integer>[]"); + + checkForFullyQualifiedClassName("java.util.Map < java.util.Set < java.lang.String > , java.lang.Integer >", "java.util.Map,java.lang.Integer>"); + } + + @Test + public void testRefClass_wildcards() { + checkForFullyQualifiedClassName("java.util.List", "java.util.List"); + checkForFullyQualifiedClassName("java.util.Map", "java.util.Map"); + checkForFullyQualifiedClassName("java.util.Map", "java.util.Map"); + } + + @Test + public void testRefClass_typevar_simple() { + checkForFullyQualifiedClassName("java.util.List"); + } + + @Test(expected = UnsupportedOperationException.class) + public void testRefClass_typevar_extends() { + checkForFullyQualifiedClassName("java.util.Set"); + } + + @Test(expected = UnsupportedOperationException.class) + public void testRefClass_typevar_multiple() { + checkForFullyQualifiedClassName("java.util.Map"); } } From 36a289a7c35daff8ebda5470ab6a920992199d68 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 29 Jan 2013 20:52:41 +0100 Subject: [PATCH 17/18] Add missing license header --- .../org/androidannotations/helper/Option.java | 15 +++++++++++++++ .../processing/rest/GetPostProcessor.java | 15 +++++++++++++++ .../processing/EBeansHolderTests.java | 15 +++++++++++++++ .../rclass/RClassFinderTest.java | 15 +++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/Option.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/Option.java index 9d06e14cc5..7f3573fc35 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/Option.java +++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/Option.java @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2010-2012 eBusiness Information, Excilys Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed To in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package org.androidannotations.helper; /** diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java index 30a5e2b14a..e01eac7e65 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2010-2012 eBusiness Information, Excilys Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed To in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package org.androidannotations.processing.rest; import java.util.ArrayList; diff --git a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java index 84e150dcd7..4fe54998f4 100644 --- a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java +++ b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/processing/EBeansHolderTests.java @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2010-2012 eBusiness Information, Excilys Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed To in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package org.androidannotations.processing; import junit.framework.Assert; diff --git a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/rclass/RClassFinderTest.java b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/rclass/RClassFinderTest.java index 6b20155d7c..1ddc92340d 100644 --- a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/rclass/RClassFinderTest.java +++ b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/rclass/RClassFinderTest.java @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2010-2012 eBusiness Information, Excilys Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed To in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package org.androidannotations.rclass; import org.androidannotations.AndroidAnnotationProcessor; From fb6432cad1245ab0a8abc37308997ef1b8b1adf2 Mon Sep 17 00:00:00 2001 From: Mathieu Boniface Date: Thu, 31 Jan 2013 19:02:15 +0100 Subject: [PATCH 18/18] Refactoring code and remove the TypeNameParser fork class from codemodel --- .../helper/APTCodeModelHelper.java | 12 ++ .../processing/EBeansHolder.java | 151 ++---------------- .../processing/rest/GetPostProcessor.java | 21 +-- 3 files changed, 38 insertions(+), 146 deletions(-) diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/APTCodeModelHelper.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/APTCodeModelHelper.java index f8e79f5f50..83d9546267 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/APTCodeModelHelper.java +++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/APTCodeModelHelper.java @@ -34,9 +34,11 @@ import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.WildcardType; import org.androidannotations.processing.EBeanHolder; import org.androidannotations.processing.EBeansHolder.Classes; + import com.sun.codemodel.JBlock; import com.sun.codemodel.JCatchBlock; import com.sun.codemodel.JClass; @@ -78,6 +80,16 @@ public JClass typeMirrorToJClass(TypeMirror type, EBeanHolder holder) { } return declaredClass; + } else if (type instanceof WildcardType) { + // TODO : At his time (01/2013), it is not possible to handle the + // super bound because code model does not offer a way to model + // statement like " ? super X" + // (see http://java.net/jira/browse/CODEMODEL-11) + WildcardType wildcardType = (WildcardType) type; + + TypeMirror extendsBound = wildcardType.getExtendsBound(); + + return typeMirrorToJClass(extendsBound, holder).wildcard(); } else if (type instanceof ArrayType) { ArrayType arrayType = (ArrayType) type; diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java index d20c378084..afd39e6924 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java +++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java @@ -20,10 +20,8 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.sql.SQLException; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -186,15 +184,24 @@ public JClass refClass(Class clazz) { } public JClass refClass(String fullyQualifiedClassName) { + + int arrayCounter = 0; + while (fullyQualifiedClassName.endsWith("[]")) { + arrayCounter++; + fullyQualifiedClassName = fullyQualifiedClassName.substring(0, fullyQualifiedClassName.length() - 2); + } + JClass refClass = loadedClasses.get(fullyQualifiedClassName); + if (refClass == null) { - if (fullyQualifiedClassName.endsWith("[]")) { - return refClass(fullyQualifiedClassName.substring(0, fullyQualifiedClassName.length() - 2)).array(); - } else { - refClass = new TypeNameParser(fullyQualifiedClassName).parseTypeName(); - loadedClasses.put(fullyQualifiedClassName, refClass); - } + refClass = codeModel.directClass(fullyQualifiedClassName); + loadedClasses.put(fullyQualifiedClassName, refClass); + } + + for (int i = 0; i < arrayCounter; i++) { + refClass = refClass.array(); } + return refClass; } @@ -227,134 +234,6 @@ JClass uniqueClass(String fullyQualifiedClassName) { return refClass; } - private final class TypeNameParser { - private final String s; - private int idx; - - public TypeNameParser(String s) { - this.s = s; - } - - /** - * Parses a type name token T (which can be potentially of the form - * Tr&ly;T1,T2,...>, or "? extends/super T".) - * - * @return the index of the character next to T. - */ - JClass parseTypeName() { - int start = idx; - String typeName = null; - - // wildcard - if (s.charAt(idx) == '?') { - idx++; - ws(); - - // Handle simple '?' - char ch = s.charAt(idx); - if (ch == '>' || ch == ',') { - return refClass(Object.class).wildcard(); - } - } else { - while (idx < s.length()) { - char ch = s.charAt(idx); - if (Character.isJavaIdentifierStart(ch) || Character.isJavaIdentifierPart(ch) || ch == '.') { - idx++; - } else { - break; - } - } - typeName = s.substring(start, idx); - } - - ws(); - String head = s.substring(idx); - if (head.startsWith("extends")) { - idx += 7; - ws(); - if (typeName != null) { - throw new UnsupportedOperationException("T extends MyObject not implemented (" + s + ")"); - } - return parseTypeName().wildcard(); - } else if (head.startsWith("super")) { - throw new UnsupportedOperationException("? super MyObject not implemented (" + s + ")"); - } - - JClass clazz = uniqueClass(typeName); - return parseSuffix(clazz); - } - - /** - * Parses additional left-associative suffixes, like type arguments and - * array specifiers. - */ - private JClass parseSuffix(JClass clazz) { - if (idx == s.length()) { - return clazz; // hit EOL - } - - char ch = s.charAt(idx); - - if (ch == '<') { - return parseSuffix(parseArguments(clazz)); - } - - if (ch == '[') { - if (s.charAt(idx + 1) == ']') { - idx += 2; - return parseSuffix(clazz.array()); - } - throw new IllegalArgumentException("Expected ']' but found " + s.substring(idx + 1)); - } - - return clazz; - } - - /** - * Skips whitespaces - */ - private void ws() { - while (idx < s.length() && Character.isWhitespace(s.charAt(idx))) { - idx++; - } - } - - /** - * Parses '<T1,T2,...,Tn>' - * - * @return the index of the character next to '>' - */ - private JClass parseArguments(JClass rawType) { - if (s.charAt(idx) != '<') { - throw new IllegalArgumentException(); - } - idx++; - ws(); - - List args = new ArrayList(); - - while (true) { - args.add(parseTypeName()); - if (idx == s.length()) { - throw new IllegalArgumentException("Missing '>' in " + s); - } - char ch = s.charAt(idx); - if (ch == '>') { - idx++; - ws(); - return rawType.narrow(args.toArray(new JClass[args.size()])); - } - - if (ch != ',') { - throw new IllegalArgumentException(s); - } - idx++; - ws(); - } - - } - } - public JCodeModel codeModel() { return codeModel; } diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java index e01eac7e65..55fececf57 100644 --- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java +++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/rest/GetPostProcessor.java @@ -34,6 +34,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.WildcardType; +import org.androidannotations.helper.APTCodeModelHelper; import org.androidannotations.helper.CanonicalNameConstants; import org.androidannotations.processing.EBeanHolder; @@ -53,8 +54,11 @@ public abstract class GetPostProcessor extends MethodProcessor { */ protected JPackage restClientPackage; + protected APTCodeModelHelper helper; + public GetPostProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationsHolder) { super(processingEnv, restImplementationsHolder); + helper = new APTCodeModelHelper(); } @Override @@ -95,7 +99,7 @@ public void retrieveReturnAndExpectedClasses(TypeMirror returnType, MethodProces String returnTypeString = returnType.toString(); JClass expectedClass = null; - JClass returnClass = holder.refClass(returnTypeString); + JClass returnClass = helper.typeMirrorToJClass(returnType, holder); if (returnTypeString.startsWith(CanonicalNameConstants.RESPONSE_ENTITY)) { DeclaredType declaredReturnType = (DeclaredType) returnType; @@ -144,13 +148,13 @@ private JClass resolveExpectedClass(TypeMirror expectedType) { // is NOT a generics, return directly if (typeArguments.isEmpty()) { - return holder.refClass(declaredType.toString()); + return helper.typeMirrorToJClass(declaredType, holder); } // is a generics, must generate a new super class TypeElement declaredElement = (TypeElement) declaredType.asElement(); - JClass baseClass = holder.refClass(declaredType.toString()).erasure(); + JClass baseClass = helper.typeMirrorToJClass(declaredType, holder).erasure(); JClass decoratedExpectedClass = retrieveDecoratedExpectedClass(declaredType, declaredElement); if (decoratedExpectedClass == null) { decoratedExpectedClass = baseClass; @@ -162,7 +166,7 @@ private JClass resolveExpectedClass(TypeMirror expectedType) { } // is not a class nor an interface, return directly - return holder.refClass(expectedType.toString()); + return helper.typeMirrorToJClass(expectedType, holder); } /** @@ -200,18 +204,15 @@ private JClass retrieveDecoratedExpectedClass(DeclaredType declaredType, TypeEle String decoratedClassNameSuffix = ""; JClass decoratedSuperClass = holder.refClass(decoratedClassName); for (TypeMirror typeArgument : declaredType.getTypeArguments()) { - String typeArgumentName = typeArgument.toString(); if (typeArgument instanceof WildcardType) { WildcardType wildcardType = (WildcardType) typeArgument; if (wildcardType.getExtendsBound() != null) { - typeArgumentName = wildcardType.getExtendsBound().toString(); + typeArgument = wildcardType.getExtendsBound(); } else if (wildcardType.getSuperBound() != null) { - typeArgumentName = wildcardType.getSuperBound().toString(); - } else { - typeArgumentName = CanonicalNameConstants.OBJECT; + typeArgument = wildcardType.getSuperBound(); } } - JClass narrowJClass = holder.refClass(typeArgumentName); + JClass narrowJClass = helper.typeMirrorToJClass(typeArgument, holder); decoratedSuperClass = decoratedSuperClass.narrow(narrowJClass); decoratedClassNameSuffix += plainName(narrowJClass); }