Skip to content
This repository has been archived by the owner on Feb 26, 2023. It is now read-only.

Commit

Permalink
Merge pull request #377 from excilys/206_converters
Browse files Browse the repository at this point in the history
RestTemplate converters set in @rest. Fixes #206.
  • Loading branch information
pyricau committed Nov 4, 2012
2 parents ed4239b + 4fe2eea commit 9c44975
Show file tree
Hide file tree
Showing 23 changed files with 335 additions and 74 deletions.
Expand Up @@ -23,5 +23,6 @@
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Rest {
String value() default "";
String rootUrl() default "";
Class<?>[] converters();
}
7 changes: 7 additions & 0 deletions AndroidAnnotations/androidannotations/pom.xml
Expand Up @@ -36,6 +36,13 @@
<version>1.6_r2</version>
<scope>test</scope>
</dependency>
<dependency>
<!-- spring-android-rest-template is required to use the Rest API -->
<groupId>org.springframework.android</groupId>
<artifactId>spring-android-rest-template</artifactId>
<version>1.0.0.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Expand Up @@ -539,7 +539,7 @@ private ModelProcessor buildModelProcessor(IRClass rClass, AndroidSystemServices
modelProcessor.register(new FragmentArgProcessor(processingEnv));
modelProcessor.register(new SystemServiceProcessor(androidSystemServices));
RestImplementationsHolder restImplementationHolder = new RestImplementationsHolder();
modelProcessor.register(new RestProcessor(restImplementationHolder));
modelProcessor.register(new RestProcessor(processingEnv, restImplementationHolder));
modelProcessor.register(new GetProcessor(processingEnv, restImplementationHolder));
modelProcessor.register(new PostProcessor(processingEnv, restImplementationHolder));
modelProcessor.register(new PutProcessor(processingEnv, restImplementationHolder));
Expand Down
Expand Up @@ -58,7 +58,6 @@ public AnnotationHelper(ProcessingEnvironment processingEnv) {
* be a subtype of itself.
*/
public boolean isSubtype(TypeMirror potentialSubtype, TypeMirror potentialSupertype) {

return processingEnv.getTypeUtils().isSubtype(potentialSubtype, potentialSupertype);
}

Expand Down Expand Up @@ -115,6 +114,10 @@ public boolean isPrivate(Element element) {
return element.getModifiers().contains(Modifier.PRIVATE);
}

public boolean isPublic(Element element) {
return element.getModifiers().contains(Modifier.PUBLIC);
}

public boolean isAbstract(Element element) {
return element.getModifiers().contains(Modifier.ABSTRACT);
}
Expand Down Expand Up @@ -308,17 +311,45 @@ public String actionName(Class<? extends Annotation> target) {
return target.getSimpleName() + "ed";
}

public DeclaredType extractAnnotationClassParameter(Element element, Class<? extends Annotation> target) {
public List<DeclaredType> extractAnnotationClassArrayParameter(Element element, Class<? extends Annotation> target, String methodName) {
AnnotationMirror annotationMirror = findAnnotationMirror(element, target);

Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();

for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
/*
* "methodName" is unset when the default value is used
*/
if (methodName.equals(entry.getKey().getSimpleName().toString())) {

AnnotationValue annotationValue = entry.getValue();

@SuppressWarnings("unchecked")
List<AnnotationValue> annotationClassArray = (List<AnnotationValue>) annotationValue.getValue();

List<DeclaredType> result = new ArrayList<DeclaredType>(annotationClassArray.size());

for (AnnotationValue annotationClassValue : annotationClassArray) {
result.add((DeclaredType) annotationClassValue.getValue());
}

return result;
}
}

return null;
}

public DeclaredType extractAnnotationClassParameter(Element element, Class<? extends Annotation> target, String methodName) {
AnnotationMirror annotationMirror = findAnnotationMirror(element, target);

Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();

for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
/*
* "value" is unset when the default value is used
* "methodName" is unset when the default value is used
*/
if ("value".equals(entry.getKey().getSimpleName().toString())) {
if (methodName.equals(entry.getKey().getSimpleName().toString())) {

AnnotationValue annotationValue = entry.getValue();

Expand All @@ -331,4 +362,8 @@ public DeclaredType extractAnnotationClassParameter(Element element, Class<? ext
return null;
}

public DeclaredType extractAnnotationClassParameter(Element element, Class<? extends Annotation> target) {
return extractAnnotationClassParameter(element, target, "value");
}

}
Expand Up @@ -99,6 +99,7 @@ public final class CanonicalNameConstants {
public static final String HTTP_METHOD = "org.springframework.http.HttpMethod";
public static final String HTTP_ENTITY = "org.springframework.http.HttpEntity";
public static final String REST_TEMPLATE = "org.springframework.web.client.RestTemplate";
public static final String HTTP_MESSAGE_CONVERTER = "org.springframework.http.converter.HttpMessageConverter";

/*
* RoboGuice
Expand Down
Expand Up @@ -20,6 +20,7 @@
import static com.googlecode.androidannotations.helper.AndroidConstants.LOG_INFO;
import static com.googlecode.androidannotations.helper.AndroidConstants.LOG_VERBOSE;
import static com.googlecode.androidannotations.helper.AndroidConstants.LOG_WARN;
import static com.googlecode.androidannotations.helper.CanonicalNameConstants.HTTP_MESSAGE_CONVERTER;
import static com.googlecode.androidannotations.helper.ModelConstants.GENERATION_SUFFIX;
import static java.util.Arrays.asList;

Expand Down Expand Up @@ -860,7 +861,7 @@ public void hasCorrectDefaultAnnotation(ExecutableElement method) {
checkDefaultAnnotation(method, DefaultString.class, "String", new DefaultAnnotationCondition() {
@Override
public boolean correctReturnType(TypeMirror returnType) {
return returnType.toString().equals("java.lang.String");
return returnType.toString().equals(CanonicalNameConstants.STRING);
}
});
}
Expand Down Expand Up @@ -1123,4 +1124,37 @@ public void componentRegistered(Element element, AndroidManifest androidManifest

}

public void validateConverters(Element element, IsValid valid) {
TypeMirror httpMessageConverterType = annotationHelper.typeElementFromQualifiedName(HTTP_MESSAGE_CONVERTER).asType();
TypeMirror httpMessageConverterTypeErased = annotationHelper.getTypeUtils().erasure(httpMessageConverterType);
List<DeclaredType> converters = annotationHelper.extractAnnotationClassArrayParameter(element, annotationHelper.getTarget(), "converters");
for (DeclaredType converterType : converters) {
TypeMirror erasedConverterType = annotationHelper.getTypeUtils().erasure(converterType);
if (annotationHelper.isSubtype(erasedConverterType, httpMessageConverterTypeErased)) {
Element converterElement = converterType.asElement();
if (converterElement.getKind().isClass()) {
if (!annotationHelper.isAbstract(converterElement)) {
List<ExecutableElement> constructors = ElementFilter.constructorsIn(converterElement.getEnclosedElements());
for (ExecutableElement constructor : constructors) {
if (annotationHelper.isPublic(constructor) && constructor.getParameters().isEmpty()) {
return;
}
}
valid.invalidate();
annotationHelper.printAnnotationError(element, "The converter class must have a public no argument constructor");
} else {
valid.invalidate();
annotationHelper.printAnnotationError(element, "The converter class must not be abstract");
}
} else {
valid.invalidate();
annotationHelper.printAnnotationError(element, "The converter class must be a class");
}
} else {
valid.invalidate();
annotationHelper.printAnnotationError(element, "The converter class must be a subtype of " + HTTP_MESSAGE_CONVERTER);
}
}

}
}
Expand Up @@ -149,6 +149,13 @@ public class Classes {
public EBeansHolder(JCodeModel codeModel) {
this.codeModel = codeModel;
classes = new Classes();
refClass(CanonicalNameConstants.STRING);
preloadJavaLangClasses();
}

private void preloadJavaLangClasses() {
loadedClasses.put(String.class.getName(), refClass(String.class));
loadedClasses.put(Object.class.getName(), refClass(Object.class));
}

public EBeanHolder create(Element element, Class<? extends Annotation> eBeanAnnotation, JDefinedClass generatedClass) {
Expand Down
Expand Up @@ -15,37 +15,45 @@
*/
package com.googlecode.androidannotations.processing.rest;

import static com.googlecode.androidannotations.helper.CanonicalNameConstants.REST_TEMPLATE;
import static com.googlecode.androidannotations.helper.CanonicalNameConstants.STRING;
import static com.sun.codemodel.JExpr._new;
import static com.sun.codemodel.JExpr._this;
import static com.sun.codemodel.JExpr.invoke;
import static com.sun.codemodel.JExpr.lit;

import java.lang.annotation.Annotation;
import java.util.List;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;

import com.googlecode.androidannotations.annotations.rest.Rest;
import com.googlecode.androidannotations.helper.AnnotationHelper;
import com.googlecode.androidannotations.helper.ModelConstants;
import com.googlecode.androidannotations.processing.EBeansHolder;
import com.googlecode.androidannotations.processing.GeneratingElementProcessor;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JVar;

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 AnnotationHelper annotationHelper;

public RestProcessor(RestImplementationsHolder restImplementationHolder) {
public RestProcessor(ProcessingEnvironment processingEnv, RestImplementationsHolder restImplementationHolder) {
annotationHelper = new AnnotationHelper(processingEnv);
this.restImplementationHolder = restImplementationHolder;
}

Expand All @@ -71,23 +79,35 @@ public void process(Element element, JCodeModel codeModel, EBeansHolder eBeansHo
holder.restImplementationClass._implements(interfaceClass);

// RestTemplate field
JClass restTemplateClass = eBeansHolder.refClass(SPRING_REST_TEMPLATE_QUALIFIED_NAME);
JClass restTemplateClass = eBeansHolder.refClass(REST_TEMPLATE);
holder.restTemplateField = holder.restImplementationClass.field(JMod.PRIVATE, restTemplateClass, "restTemplate");

// RootUrl field
JClass stringClass = eBeansHolder.refClass(JAVA_STRING_QUALIFIED_NAME);
JClass stringClass = eBeansHolder.refClass(STRING);
holder.rootUrlField = holder.restImplementationClass.field(JMod.PRIVATE, stringClass, "rootUrl");

// Default constructor
JMethod defaultConstructor = holder.restImplementationClass.constructor(JMod.PUBLIC);
defaultConstructor.body().assign(holder.restTemplateField, JExpr._new(restTemplateClass));
defaultConstructor.body().assign(holder.rootUrlField, JExpr.lit(typeElement.getAnnotation(Rest.class).value()));
{
// Constructor
JMethod constructor = holder.restImplementationClass.constructor(JMod.PUBLIC);
JBlock constructorBody = constructor.body();
constructorBody.assign(holder.restTemplateField, _new(restTemplateClass));

{
// Converters
List<DeclaredType> converters = annotationHelper.extractAnnotationClassArrayParameter(element, getTarget(), "converters");
for (DeclaredType converterType : converters) {
JClass converterClass = eBeansHolder.refClass(converterType.toString());
constructorBody.add(invoke(holder.restTemplateField, "getMessageConverters").invoke("add").arg(_new(converterClass)));
}
}
constructorBody.assign(holder.rootUrlField, lit(typeElement.getAnnotation(Rest.class).rootUrl()));
}

// Implement getRestTemplate method
List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
List<ExecutableElement> methods = ElementFilter.methodsIn(enclosedElements);
for (ExecutableElement method : methods) {
if (method.getParameters().size() == 0 && method.getReturnType().toString().equals(SPRING_REST_TEMPLATE_QUALIFIED_NAME)) {
if (method.getParameters().size() == 0 && method.getReturnType().toString().equals(REST_TEMPLATE)) {
String methodName = method.getSimpleName().toString();
JMethod getRestTemplateMethod = holder.restImplementationClass.method(JMod.PUBLIC, restTemplateClass, methodName);
getRestTemplateMethod.annotate(Override.class);
Expand All @@ -100,7 +120,7 @@ public void process(Element element, JCodeModel codeModel, EBeansHolder eBeansHo
List<? extends VariableElement> parameters = method.getParameters();
if (parameters.size() == 1 && method.getReturnType().getKind() == TypeKind.VOID) {
VariableElement firstParameter = parameters.get(0);
if (firstParameter.asType().toString().equals(SPRING_REST_TEMPLATE_QUALIFIED_NAME)) {
if (firstParameter.asType().toString().equals(REST_TEMPLATE)) {
String methodName = method.getSimpleName().toString();
JMethod setRestTemplateMethod = holder.restImplementationClass.method(JMod.PUBLIC, codeModel.VOID, methodName);
setRestTemplateMethod.annotate(Override.class);
Expand All @@ -118,7 +138,7 @@ public void process(Element element, JCodeModel codeModel, EBeansHolder eBeansHo
List<? extends VariableElement> parameters = method.getParameters();
if (parameters.size() == 1 && method.getReturnType().getKind() == TypeKind.VOID) {
VariableElement firstParameter = parameters.get(0);
if (firstParameter.asType().toString().equals(JAVA_STRING_QUALIFIED_NAME) && method.getSimpleName().toString().equals("setRootUrl")) {
if (firstParameter.asType().toString().equals(STRING) && method.getSimpleName().toString().equals("setRootUrl")) {
JMethod setRootUrlMethod = holder.restImplementationClass.method(JMod.PUBLIC, codeModel.VOID, method.getSimpleName().toString());
setRootUrlMethod.annotate(Override.class);

Expand Down
Expand Up @@ -44,7 +44,6 @@ public Class<? extends Annotation> getTarget() {

@Override
public boolean validate(Element element, AnnotationElements validatedElements) {

IsValid valid = new IsValid();

TypeElement typeElement = (TypeElement) element;
Expand All @@ -61,6 +60,8 @@ public boolean validate(Element element, AnnotationElements validatedElements) {

validatorHelper.unannotatedMethodReturnsRestTemplate(typeElement, valid);

validatorHelper.validateConverters(element, valid);

return valid.isValid();
}

Expand Down
@@ -0,0 +1,7 @@
package com.googlecode.androidannotations.rest;

import org.springframework.http.converter.AbstractHttpMessageConverter;

public abstract class AbstractConverter<T> extends AbstractHttpMessageConverter<T> {

}
@@ -0,0 +1,8 @@
package com.googlecode.androidannotations.rest;

import com.googlecode.androidannotations.annotations.rest.Rest;

@Rest(converters = {})
public class ClassClient {

}
@@ -0,0 +1,8 @@
package com.googlecode.androidannotations.rest;

import com.googlecode.androidannotations.annotations.rest.Rest;

@Rest(converters = { AbstractConverter.class })
public interface ClientWithAbstractConverter {

}
@@ -0,0 +1,8 @@
package com.googlecode.androidannotations.rest;

import com.googlecode.androidannotations.annotations.rest.Rest;

@Rest(converters = {})
public interface ClientWithNoConverters {

}
@@ -0,0 +1,8 @@
package com.googlecode.androidannotations.rest;

import com.googlecode.androidannotations.annotations.rest.Rest;

@Rest(converters = { Object.class })
public interface ClientWithNonConverter {

}
@@ -0,0 +1,10 @@
package com.googlecode.androidannotations.rest;

import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;

import com.googlecode.androidannotations.annotations.rest.Rest;

@Rest(converters = { MappingJacksonHttpMessageConverter.class })
public interface ClientWithValidConverter {

}
@@ -0,0 +1,8 @@
package com.googlecode.androidannotations.rest;

import com.googlecode.androidannotations.annotations.rest.Rest;

@Rest(converters = { WrongConstructorConverter.class })
public interface ClientWithWrongConstructorConverter {

}

0 comments on commit 9c44975

Please sign in to comment.