content: /**
   * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
   * Copyright (C) 2016-2020 the AndroidAnnotations project
   *
   * 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.rest.spring.helper;
  
  import static java.util.Arrays.asList;
  import static org.androidannotations.rest.spring.helper.RestSpringClasses.CLIENT_HTTP_REQUEST_FACTORY;
  import static org.androidannotations.rest.spring.helper.RestSpringClasses.CLIENT_HTTP_REQUEST_INTERCEPTOR;
  import static org.androidannotations.rest.spring.helper.RestSpringClasses.FORM_HTTP_MESSAGE_CONVERTER;
  import static org.androidannotations.rest.spring.helper.RestSpringClasses.HTTP_MESSAGE_CONVERTER;
  import static org.androidannotations.rest.spring.helper.RestSpringClasses.REST_CLIENT_EXCEPTION;
  import static org.androidannotations.rest.spring.helper.RestSpringClasses.REST_TEMPLATE;
  
  import java.lang.annotation.Annotation;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  import javax.lang.model.element.AnnotationMirror;
  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.element.VariableElement;
  import javax.lang.model.type.DeclaredType;
  import javax.lang.model.type.TypeKind;
  import javax.lang.model.type.TypeMirror;
  import javax.lang.model.util.ElementFilter;
  import javax.lang.model.util.Elements;
  
  import org.androidannotations.AndroidAnnotationsEnvironment;
  import org.androidannotations.ElementValidation;
  import org.androidannotations.annotations.EBean;
  import org.androidannotations.helper.CanonicalNameConstants;
  import org.androidannotations.helper.TargetAnnotationHelper;
  import org.androidannotations.helper.ValidatorHelper;
  import org.androidannotations.rest.spring.annotations.Body;
  import org.androidannotations.rest.spring.annotations.Delete;
  import org.androidannotations.rest.spring.annotations.Field;
  import org.androidannotations.rest.spring.annotations.Get;
  import org.androidannotations.rest.spring.annotations.Head;
  import org.androidannotations.rest.spring.annotations.Options;
  import org.androidannotations.rest.spring.annotations.Part;
  import org.androidannotations.rest.spring.annotations.Patch;
  import org.androidannotations.rest.spring.annotations.Path;
  import org.androidannotations.rest.spring.annotations.Post;
  import org.androidannotations.rest.spring.annotations.Put;
  import org.androidannotations.rest.spring.annotations.Rest;
  import org.androidannotations.rest.spring.api.RestClientErrorHandling;
  import org.androidannotations.rest.spring.api.RestClientHeaders;
  import org.androidannotations.rest.spring.api.RestClientRootUrl;
  import org.androidannotations.rest.spring.api.RestClientSupport;
  
  public class RestSpringValidatorHelper extends ValidatorHelper {
  
  	private static final List<String> VALID_REST_INTERFACES = asList(RestClientHeaders.class.getName(), RestClientErrorHandling.class.getName(), RestClientRootUrl.class.getName(),
  			RestClientSupport.class.getName());
  	private static final List<Class<? extends Annotation>> REST_ANNOTATION_CLASSES = Arrays.asList(Get.class, Head.class, Options.class, Post.class, Put.class, Patch.class, Delete.class);
  
  	private static final String METHOD_NAME_SET_ROOT_URL = "setRootUrl";
  	private static final String METHOD_NAME_SET_AUTHENTICATION = "setAuthentication";
  	private static final String METHOD_NAME_SET_BEARER_AUTH = "setBearerAuth";
  	private static final String METHOD_NAME_GET_COOKIE = "getCookie";
  	private static final String METHOD_NAME_GET_HEADER = "getHeader";
  
  	private static final String METHOD_NAME_GET_ROOT_URL = "getRootUrl";
  
  	private final RestAnnotationHelper restAnnotationHelper;
  
  	public RestSpringValidatorHelper(AndroidAnnotationsEnvironment environment, String target) {
  		super(new TargetAnnotationHelper(environment, target));
  		restAnnotationHelper = new RestAnnotationHelper(environment, target);
  	}
  
  	public void doesNotExtendInvalidInterfaces(TypeElement element, ElementValidation valid) {
  		if (element.getInterfaces().size() > 0) {
  			boolean isValid = true;
  
  			for (TypeMirror iface : element.getInterfaces()) {
  				if (!VALID_REST_INTERFACES.contains(iface.toString())) {
  					isValid = false;
  					break;
  				}
  			}
  
  			if (!isValid) {
  				valid.addError("%s interfaces can only extend the following interfaces: " + VALID_REST_INTERFACES);
  			}
  		}
  	}
  
  	public void enclosingElementHasRestAnnotation(Element element, ElementValidation valid) {
  		String error = "can only be used in an interface annotated with";
  		enclosingElementHasAnnotation(Rest.class, element, valid, error);
  	}
  
  	public void enclosingElementHasOneOfRestMethodAnnotations(Element element, ElementValidation validation) {
  		enclosingElementHasOneOfAnnotations(element, REST_ANNOTATION_CLASSES, validation);
  	}
  
  	public void unannotatedMethodReturnsRestTemplate(TypeElement typeElement, ElementValidation valid) {
  		List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
  		boolean foundGetRestTemplateMethod = false;
  		boolean foundSetRestTemplateMethod = false;
  		boolean foundSetAuthenticationMethod = false;
  		boolean foundSetBearerAuthMethod = false;
  		boolean foundSetRootUrlMethod = false;
  		boolean foundGetCookieMethod = false;
  		boolean foundGetHeaderMethod = false;
  		boolean foundGetRootUrlMethod = false;
  
  		for (Element enclosedElement : enclosedElements) {
  			if (enclosedElement.getKind() != ElementKind.METHOD) {
  				valid.addError(enclosedElement, "Only methods are allowed in a %s annotated interface");
  			} else {
  
  				boolean hasRestAnnotation = false;
  				for (Class<? extends Annotation> annotationClass : REST_ANNOTATION_CLASSES) {
  					if (enclosedElement.getAnnotation(annotationClass) != null) {
  						hasRestAnnotation = true;
  						break;
  					}
  				}
  
  				if (!hasRestAnnotation) {
  
  					ExecutableElement executableElement = (ExecutableElement) enclosedElement;
  					TypeMirror returnType = executableElement.getReturnType();
  					String simpleName = executableElement.getSimpleName().toString();
  
  					if (returnType.toString().equals(REST_TEMPLATE)) {
  						if (executableElement.getParameters().size() > 0) {
  							valid.addError(enclosedElement,
  									"The method returning a RestTemplate should not declare any parameter in a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  						} else {
  							if (foundGetRestTemplateMethod) {
  								valid.addError(enclosedElement,
  										"Only one method should declare returning a RestTemplate in a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  							} else {
  								foundGetRestTemplateMethod = true;
  							}
  						}
  					} else if (simpleName.equals(METHOD_NAME_GET_ROOT_URL)) {
  						if (!returnType.toString().equals(CanonicalNameConstants.STRING)) {
  							valid.addError(enclosedElement, "The method getRootUrl must return String on a %s annotated interface");
  						}
  
  						if (executableElement.getParameters().size() != 0) {
  							valid.addError(enclosedElement, "The method getRootUrl cannot have parameters on a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  						}
  
  						if (!foundGetRootUrlMethod) {
  							foundGetRootUrlMethod = true;
  						} else {
  							valid.addError(enclosedElement, "The can be only one getRootUrl method on a %s annotated interface");
  						}
  					} else if (returnType.getKind() == TypeKind.VOID) {
  						List<? extends VariableElement> parameters = executableElement.getParameters();
  						if (parameters.size() == 1) {
  							VariableElement firstParameter = parameters.get(0);
  							if (firstParameter.asType().toString().equals(REST_TEMPLATE)) {
  								if (!foundSetRestTemplateMethod) {
  									foundSetRestTemplateMethod = true;
  								} else {
  									valid.addError(enclosedElement,
  											"You can only have oneRestTemplate setter method on a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  								}
  							} else if (executableElement.getSimpleName().toString().equals(METHOD_NAME_SET_ROOT_URL) && !foundSetRootUrlMethod) {
  								foundSetRootUrlMethod = true;
  							} else if (executableElement.getSimpleName().toString().equals(METHOD_NAME_SET_AUTHENTICATION) && !foundSetAuthenticationMethod) {
  								foundSetAuthenticationMethod = true;
  							} else if (executableElement.getSimpleName().toString().equals(METHOD_NAME_SET_BEARER_AUTH) && !foundSetBearerAuthMethod) {
  								foundSetBearerAuthMethod = true;
  							} else {
  								valid.addError(enclosedElement, "The method to set a RestTemplate should have only one RestTemplate parameter on a " + TargetAnnotationHelper.annotationName(Rest.class)
  										+ " annotated interface");
  
  							}
  						} else if (parameters.size() == 2) {
  							VariableElement firstParameter = parameters.get(0);
  							VariableElement secondParameter = parameters.get(1);
  							if (!(firstParameter.asType().toString().equals(CanonicalNameConstants.STRING) && secondParameter.asType().toString().equals(CanonicalNameConstants.STRING))) {
  								valid.addError(enclosedElement, "The method to set headers, cookies, or HTTP Basic Auth should have only String parameters on a "
  										+ TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  							}
  						} else {
  							valid.addError(enclosedElement,
  									"The method to set a RestTemplate should have only one RestTemplate parameter on a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  						}
  					} else if (returnType.toString().equals(CanonicalNameConstants.STRING)) {
  						List<? extends VariableElement> parameters = executableElement.getParameters();
  						if (parameters.size() == 1) {
  							VariableElement firstParameter = parameters.get(0);
  							if (firstParameter.asType().toString().equals(CanonicalNameConstants.STRING)) {
  								if (executableElement.getSimpleName().toString().equals(METHOD_NAME_GET_COOKIE) && !foundGetCookieMethod) {
  									foundGetCookieMethod = true;
  								} else if (executableElement.getSimpleName().toString().equals(METHOD_NAME_GET_HEADER) && !foundGetHeaderMethod) {
  									foundGetHeaderMethod = true;
  								} else {
  									valid.addError(enclosedElement, "Only one getCookie(String) and one getHeader(String) method are allowed on a " + TargetAnnotationHelper.annotationName(Rest.class)
  											+ " annotated interface");
  								}
  							} else {
  								valid.addError(enclosedElement,
  										"Only getCookie(String) and getHeader(String) can return a String on a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface");
  							}
  
  						} else {
  							valid.addError(enclosedElement, "The only methods that can return a String on a " + TargetAnnotationHelper.annotationName(Rest.class)
  									+ " annotated interface are getCookie(String) and getHeader(String)");
  						}
  					} else {
  						valid.addError(enclosedElement, "All methods should be annotated in a " + TargetAnnotationHelper.annotationName(Rest.class)
  								+ " annotated interface, except the ones that returns or set a RestTemplate");
  					}
  				}
  			}
  		}
  	}
  
  	public void validateConverters(Element element, ElementValidation valid) {
  		TypeMirror httpMessageConverterType = annotationHelper.typeElementFromQualifiedName(HTTP_MESSAGE_CONVERTER).asType();
  		TypeMirror httpMessageConverterTypeErased = annotationHelper.getTypeUtils().erasure(httpMessageConverterType);
  		List<DeclaredType> converters = annotationHelper.extractAnnotationClassArrayParameter(element, annotationHelper.getTarget(), "converters");
  
  		if (converters == null || converters.isEmpty()) {
  			valid.addError(element, "At least one converter is required");
  			return;
  		}
  
  		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)) {
  						if (converterElement.getAnnotation(EBean.class) == null) {
  							List<ExecutableElement> constructors = ElementFilter.constructorsIn(converterElement.getEnclosedElements());
  							boolean hasPublicWithNoArgumentConstructor = false;
  							for (ExecutableElement constructor : constructors) {
  								if (annotationHelper.isPublic(constructor) && constructor.getParameters().isEmpty()) {
  									hasPublicWithNoArgumentConstructor = true;
  								}
  							}
  							if (!hasPublicWithNoArgumentConstructor) {
  								valid.addError("The converter class must have a public no argument constructor");
  							}
  						} else {
  							typeIsValid(EBean.class, converterType, valid);
  						}
  					} else {
  						valid.addError("The converter class must not be abstract");
  					}
  				} else {
  					valid.addError("The converter class must be a class");
  				}
  			} else {
  				valid.addError("The converter class must be a subtype of " + HTTP_MESSAGE_CONVERTER);
  			}
  		}
  	}
  
  	public void validateInterceptors(Element element, ElementValidation valid) {
  		TypeMirror clientHttpRequestInterceptorType = annotationHelper.typeElementFromQualifiedName(CLIENT_HTTP_REQUEST_INTERCEPTOR).asType();
  		TypeMirror clientHttpRequestInterceptorTypeErased = annotationHelper.getTypeUtils().erasure(clientHttpRequestInterceptorType);
  		List<DeclaredType> interceptors = annotationHelper.extractAnnotationClassArrayParameter(element, annotationHelper.getTarget(), "interceptors");
  		if (interceptors == null) {
  			return;
  		}
  		for (DeclaredType interceptorType : interceptors) {
  			TypeMirror erasedInterceptorType = annotationHelper.getTypeUtils().erasure(interceptorType);
  			if (annotationHelper.isSubtype(erasedInterceptorType, clientHttpRequestInterceptorTypeErased)) {
  				Element interceptorElement = interceptorType.asElement();
  				if (interceptorElement.getKind().isClass()) {
  					if (!annotationHelper.isAbstract(interceptorElement)) {
  						if (interceptorElement.getAnnotation(EBean.class) == null) {
  							List<ExecutableElement> constructors = ElementFilter.constructorsIn(interceptorElement.getEnclosedElements());
  							boolean hasPublicWithNoArgumentConstructor = false;
  							for (ExecutableElement constructor : constructors) {
  								if (annotationHelper.isPublic(constructor) && constructor.getParameters().isEmpty()) {
  									hasPublicWithNoArgumentConstructor = true;
  								}
  							}
  							if (!hasPublicWithNoArgumentConstructor) {
  								valid.addError("The interceptor class must have a public no argument constructor or be annotated with @EBean");
  							}
  						} else {
  							typeIsValid(EBean.class, interceptorType, valid);
  						}
  					} else {
  						valid.addError("The interceptor class must not be abstract");
  					}
  				} else {
  					valid.addError("The interceptor class must be a class");
  				}
  			} else {
  				valid.addError("The interceptor class must be a subtype of " + CLIENT_HTTP_REQUEST_INTERCEPTOR);
  			}
  		}
  	}
  
  	public void validateRestSimpleParameter(Element element, String requiredClass, String parameterName, ElementValidation validation) {
  		TypeMirror requiredType = annotationHelper.typeElementFromQualifiedName(requiredClass).asType();
  		DeclaredType paramterType = annotationHelper.extractAnnotationClassParameter(element, annotationHelper.getTarget(), parameterName);
  		if (paramterType != null) {
  			if (annotationHelper.isSubtype(paramterType, requiredType)) {
  				Element parameterElement = paramterType.asElement();
  				if (parameterElement.getKind().isClass()) {
  					if (!annotationHelper.isAbstract(parameterElement)) {
  						if (parameterElement.getAnnotation(EBean.class) != null) {
  							typeIsValid(EBean.class, paramterType, validation);
  							return;
  						}
  						List<ExecutableElement> constructors = ElementFilter.constructorsIn(parameterElement.getEnclosedElements());
  						for (ExecutableElement constructor : constructors) {
  							if (annotationHelper.isPublic(constructor) && constructor.getParameters().isEmpty()) {
  								return;
  							}
  						}
  						validation.addError(element, "The " + parameterName + " class must have a public no argument constructor or must be annotated with @EBean");
  					} else {
  						validation.addError(element, "The " + parameterName + " class must not be abstract");
  					}
  				} else {
  					validation.addError(element, "The " + parameterName + " class must be a class");
  				}
  			} else {
  				validation.addError(element, "The " + parameterName + " class must be a subtype of " + requiredClass);
  			}
  		}
  	}
  
  	public void validateRequestFactory(Element element, ElementValidation validation) {
  		validateRestSimpleParameter(element, CLIENT_HTTP_REQUEST_FACTORY, "requestFactory", validation);
  	}
  
  	public void validateResponseErrorHandler(Element element, ElementValidation validation) {
  		validateRestSimpleParameter(element, RestSpringClasses.RESPONSE_ERROR_HANDLER, "responseErrorHandler", validation);
  	}
  
  	public void throwsOnlyRestClientException(ExecutableElement element, ElementValidation valid) {
  		List<? extends TypeMirror> thrownTypes = element.getThrownTypes();
  		if (thrownTypes.size() > 0) {
  			if (thrownTypes.size() > 1 || !thrownTypes.get(0).toString().equals(REST_CLIENT_EXCEPTION)) {
  				valid.addError("%s annotated methods can only declare throwing a RestClientException");
  			}
  		}
  	}
  
  	public void urlVariableNamesExistInParameters(ExecutableElement element, Set<String> variableNames, ElementValidation valid) {
  
  		List<? extends VariableElement> parameters = element.getParameters();
  
  		Set<String> parametersName = new HashSet<>();
  		for (VariableElement parameter : parameters) {
  			if (parameter.getAnnotation(Path.class) == null) {
  				continue;
  			}
  
  			String nameToAdd = restAnnotationHelper.getUrlVariableCorrespondingTo(parameter);
  
  			if (parametersName.contains(nameToAdd)) {
  				valid.addError(element, "%s has multiple method parameters which correspond to the same url variable");
  				return;
  			}
  			parametersName.add(nameToAdd);
  		}
  
  		String[] cookiesToUrl = restAnnotationHelper.requiredUrlCookies(element);
  		if (cookiesToUrl != null) {
  			Collections.addAll(parametersName, cookiesToUrl);
  		}
  
  		for (String variableName : variableNames) {
  			if (!parametersName.contains(variableName)) {
  				valid.addError("%s annotated method has an url variable which name could not be found in the method parameters: " + variableName);
  				return;
  			}
  		}
  	}
  
  	public void doesNotMixRequestEntityAnnotations(ExecutableElement element, ElementValidation validation) {
  		int numberOfRequestEntityFound = 0;
  		boolean partFound = false;
  		boolean fieldFound = false;
  		boolean bodyFound = false;
  
  		for (VariableElement parameter : element.getParameters()) {
  			Part part = parameter.getAnnotation(Part.class);
  			if (part != null && !partFound) {
  				partFound = true;
  				numberOfRequestEntityFound++;
  			}
  
  			Field field = parameter.getAnnotation(Field.class);
  			if (field != null && !fieldFound) {
  				fieldFound = true;
  				numberOfRequestEntityFound++;
  			}
  
  			Body body = parameter.getAnnotation(Body.class);
  			if (body != null && !bodyFound) {
  				bodyFound = true;
  				numberOfRequestEntityFound++;
  			}
  		}
  
  		if (numberOfRequestEntityFound > 1) {
  			validation.addError(element, "Only one of @Part, @Field and @Body annotations can be used on the same method's parameters, not both.");
  		}
  	}
  
  	public void urlVariableNameExistsInEnclosingAnnotation(Element element, ElementValidation validation) {
  		Set<String> validRestMethodAnnotationNames = new HashSet<>();
  
  		for (Class<? extends Annotation> validAnnotation : REST_ANNOTATION_CLASSES) {
  			validRestMethodAnnotationNames.add(validAnnotation.getCanonicalName());
  		}
  
  		String url = null;
  
  		for (AnnotationMirror annotationMirror : element.getEnclosingElement().getAnnotationMirrors()) {
  			if (validRestMethodAnnotationNames.contains(annotationMirror.getAnnotationType().toString())) {
  				url = restAnnotationHelper.extractAnnotationParameter(element.getEnclosingElement(), annotationMirror.getAnnotationType().toString(), "value");
  				break;
  			}
  		}
  
  		Set<String> urlVariableNames = restAnnotationHelper.extractUrlVariableNames(url);
  
  		String expectedUrlVariableName = restAnnotationHelper.getUrlVariableCorrespondingTo((VariableElement) element);
  
  		if (!urlVariableNames.contains(expectedUrlVariableName)) {
  			validation.addError(element, "%s annotated parameter is has no corresponding url variable");
  		}
  	}
  
  	public void hasSpringAndroidJars(ElementValidation valid) {
  		Elements elementUtils = annotationHelper.getElementUtils();
  		if (elementUtils.getTypeElement(REST_TEMPLATE) == null) {
  			valid.addError("Could not find the SpringAndroid framework in the classpath, the following class is missing: " + REST_TEMPLATE);
  		}
  	}
  
  	public void hasHttpHeadersReturnType(ExecutableElement element, ElementValidation valid) {
  		String returnType = element.getReturnType().toString();
  		if (!"org.springframework.http.HttpHeaders".equals(returnType)) {
  			valid.addError("%s annotated methods can only return a HttpHeaders, not " + returnType);
  		}
  	}
  
  	public void hasSetOfHttpMethodReturnType(ExecutableElement element, ElementValidation valid) {
  		TypeMirror returnType = element.getReturnType();
  		String returnTypeString = returnType.toString();
  		if (!"java.util.Set<org.springframework.http.HttpMethod>".equals(returnTypeString)) {
  			valid.addError("%s annotated methods can only return a Set of HttpMethod, not " + returnTypeString);
  		} else {
  			DeclaredType declaredType = (DeclaredType) returnType;
  			List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
  			if (typeArguments.size() != 1) {
  				valid.addError("%s annotated methods can only return a parameterized Set (with HttpMethod)");
  			} else {
  				TypeMirror typeArgument = typeArguments.get(0);
  				if (!typeArgument.toString().equals("org.springframework.http.HttpMethod")) {
  					valid.addError("%s annotated methods can only return a parameterized Set of HttpMethod, not " + typeArgument.toString());
  				}
  			}
  		}
  	}
  
  	public int numberOfBodyAnnotatedParameter(ExecutableElement element) {
  		return numberOfElementParameterHasAnnotation(element, Body.class);
  	}
  
  	public int numberOfPathAnnotatedParameter(ExecutableElement element) {
  		return numberOfElementParameterHasAnnotation(element, Path.class);
  	}
  
  	public int numberOfRequiresCookieInUrl(ExecutableElement element) {
  		String[] cookiesToUrl = restAnnotationHelper.requiredUrlCookies(element);
  		if (cookiesToUrl == null) {
  			return 0;
  		} else {
  			return cookiesToUrl.length;
  		}
  	}
  
  	public int numberOfPartAnnotatedParameter(ExecutableElement element) {
  		return numberOfElementParameterHasAnnotation(element, Part.class);
  	}
  
  	public int numberOfFieldAnnotatedParameter(ExecutableElement element) {
  		return numberOfElementParameterHasAnnotation(element, Field.class);
  	}
  
  	public void doesNotHaveDuplicateFieldAndPartName(ExecutableElement element, ElementValidation validation) {
  		if (restAnnotationHelper.extractFieldAndPartParameters(element) == null) {
  			validation.addError(element, "%s annotated method has multiple form parameters with the same name");
  		}
  	}
  
  	public void doesNotHaveBodyAnnotatedParameter(ExecutableElement element, ElementValidation validation) {
  		if (numberOfBodyAnnotatedParameter(element) != 0) {
  			validation.addError(element, "%s parameters must not have @Body parameter");
  		}
  	}
  
  	public void doesNotHavePartAnnotatedParameter(ExecutableElement element, ElementValidation validation) {
  		if (numberOfPartAnnotatedParameter(element) != 0) {
  			validation.addError(element, "%s parameters must not have @Part parameter");
  		}
  	}
  
  	public void doesNotHaveFieldAnnotatedParameter(ExecutableElement element, ElementValidation validation) {
  		if (numberOfFieldAnnotatedParameter(element) != 0) {
  			validation.addError(element, "%s parameters must not have @Field parameter");
  		}
  	}
  
  	public void doesNotHaveRequestEntityAnnotatedParameters(ExecutableElement element, ElementValidation validation) {
  		doesNotHavePartAnnotatedParameter(element, validation);
  		doesNotHaveFieldAnnotatedParameter(element, validation);
  		doesNotHaveBodyAnnotatedParameter(element, validation);
  	}
  
  	public void doesNotHavePathAnnotation(Element element, ElementValidation validation) {
  		doesNotHaveAnnotation(element, Path.class, validation);
  	}
  
  	public void doesNotHaveFieldAnnotation(Element element, ElementValidation validation) {
  		doesNotHaveAnnotation(element, Field.class, validation);
  	}
  
  	public void doesNotHavePartAnnotation(Element element, ElementValidation validation) {
  		doesNotHaveAnnotation(element, Part.class, validation);
  	}
  
  	public void doesNotHaveBodyAnnotation(Element element, ElementValidation validation) {
  		doesNotHaveAnnotation(element, Body.class, validation);
  	}
  
  	public void restInterfaceHasFormConverter(Element element, ElementValidation validation) {
  		Element restInterface = element.getEnclosingElement().getEnclosingElement();
  
  		if (restInterface.getAnnotation(Rest.class) == null) {
  			return;
  		}
  
  		List<DeclaredType> converters = annotationHelper.extractAnnotationClassArrayParameter(restInterface, Rest.class.getCanonicalName(), "converters");
  
  		boolean formConverterFound = false;
  
  		TypeMirror formConverter = annotationHelper.getElementUtils().getTypeElement(FORM_HTTP_MESSAGE_CONVERTER).asType();
  
  		for (DeclaredType converter : converters) {
  			if (formConverter != null && annotationHelper.isSubtype(converter, formConverter)) {
  				formConverterFound = true;
  				break;
  			}
  		}
  
  		if (!formConverterFound) {
  			validation.addError(element, "%s annotated method parameter must be in a @Rest annotated interface which uses at least one " //
  					+ FORM_HTTP_MESSAGE_CONVERTER + " (or subtype)");
  		}
  	}
  
  	public void elementHasOneOfRestMethodAnnotations(Element element, ElementValidation validation) {
  		hasOneOfAnnotations(element, element, REST_ANNOTATION_CLASSES, validation);
  	}
  
  	public void usesSpringAndroid2(Element element, ElementValidation validation) {
  		if (environment().getProcessingEnvironment().getElementUtils().getTypeElement(RestSpringClasses.PARAMETERIZED_TYPE_REFERENCE) == null) {
  			validation.addError(element, "To use %s annotated method you must add Spring Android Rest Template 2.0 to your classpath");
  		}
  	}
  
  	public void hasOneOrZeroBodyParameter(ExecutableElement element, ElementValidation validation) {
  		if (validation.isValid() && numberOfBodyAnnotatedParameter(element) > 1) {
  			validation.addError(element, "%s parameters must not have more than one @Body annotation.");
  		}
  	}
  
  	public void hasAnnotatedAllParameters(ExecutableElement element, ElementValidation validation) {
  		if (!validation.isValid()) {
  			return;
  		}
  
  		Set<String> urlVariableNames = restAnnotationHelper.extractUrlVariableNames(element);
  		if (urlVariableNames.size() != numberOfPathAnnotatedParameter(element) + numberOfRequiresCookieInUrl(element)) {
  			validation.addError(element, "%s must have url variables corresponding to the @Path or @RequiresCookieInUrl annotation");
  		}
  
  		for (VariableElement variableElement : element.getParameters()) {
  			if (!restAnnotationHelper.hasRestApiMethodParameterAnnotation(variableElement)) {
  				validation.addError(element, "%s method parameters '" + variableElement + "' must be annotated");
  			}
  		}
  	}
  }
  