Java tutorial
/** * 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.helper; import static com.googlecode.androidannotations.helper.AndroidConstants.LOG_DEBUG; import static com.googlecode.androidannotations.helper.AndroidConstants.LOG_ERROR; 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; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; 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.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ErrorType; 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 com.googlecode.androidannotations.annotations.EActivity; import com.googlecode.androidannotations.annotations.EApplication; import com.googlecode.androidannotations.annotations.EBean; import com.googlecode.androidannotations.annotations.EFragment; import com.googlecode.androidannotations.annotations.EProvider; import com.googlecode.androidannotations.annotations.EReceiver; import com.googlecode.androidannotations.annotations.EService; import com.googlecode.androidannotations.annotations.EView; import com.googlecode.androidannotations.annotations.EViewGroup; import com.googlecode.androidannotations.annotations.Trace; import com.googlecode.androidannotations.annotations.ViewById; import com.googlecode.androidannotations.annotations.rest.Delete; import com.googlecode.androidannotations.annotations.rest.Get; import com.googlecode.androidannotations.annotations.rest.Head; import com.googlecode.androidannotations.annotations.rest.Options; import com.googlecode.androidannotations.annotations.rest.Post; import com.googlecode.androidannotations.annotations.rest.Put; import com.googlecode.androidannotations.annotations.rest.Rest; import com.googlecode.androidannotations.annotations.sharedpreferences.DefaultBoolean; import com.googlecode.androidannotations.annotations.sharedpreferences.DefaultFloat; import com.googlecode.androidannotations.annotations.sharedpreferences.DefaultInt; import com.googlecode.androidannotations.annotations.sharedpreferences.DefaultLong; import com.googlecode.androidannotations.annotations.sharedpreferences.DefaultString; import com.googlecode.androidannotations.annotations.sharedpreferences.SharedPref; import com.googlecode.androidannotations.api.sharedpreferences.SharedPreferencesHelper; import com.googlecode.androidannotations.model.AndroidSystemServices; import com.googlecode.androidannotations.model.AnnotationElements; import com.googlecode.androidannotations.processing.InstanceStateProcessor; import com.googlecode.androidannotations.validation.IsValid; public class ValidatorHelper { private static final List<String> ANDROID_SHERLOCK_MENU_ITEM_QUALIFIED_NAMES = asList( CanonicalNameConstants.MENU_ITEM, CanonicalNameConstants.SHERLOCK_MENU_ITEM); private static final List<String> ANDROID_FRAGMENT_QUALIFIED_NAMES = asList(CanonicalNameConstants.FRAGMENT, CanonicalNameConstants.SUPPORT_V4_FRAGMENT); private static final String METHOD_NAME_SET_ROOT_URL = "setRootUrl"; private static final List<String> VALID_PREF_RETURN_TYPES = Arrays.asList("int", "boolean", "float", "long", CanonicalNameConstants.STRING); private static final List<String> INVALID_PREF_METHOD_NAMES = Arrays.asList("edit", "getSharedPreferences", "clear", "getEditor", "apply"); private static final Collection<Integer> VALID_LOG_LEVELS = Arrays.asList(LOG_VERBOSE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR); @SuppressWarnings("unchecked") private static final List<Class<? extends Annotation>> VALID_ENHANCED_VIEW_SUPPORT_ANNOTATIONS = asList( EActivity.class, EViewGroup.class, EView.class, EBean.class, EFragment.class); @SuppressWarnings("unchecked") private static final List<Class<? extends Annotation>> VALID_ENHANCED_COMPONENT_ANNOTATIONS = asList( EApplication.class, EActivity.class, EViewGroup.class, EView.class, EBean.class, EService.class, EReceiver.class, EProvider.class, EFragment.class); protected final TargetAnnotationHelper annotationHelper; public ValidatorHelper(TargetAnnotationHelper targetAnnotationHelper) { annotationHelper = targetAnnotationHelper; } public void isNotFinal(Element element, IsValid valid) { if (annotationHelper.isFinal(element)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s cannot be used on a final element"); } } public void isNotSynchronized(Element element, IsValid valid) { if (annotationHelper.isSynchronized(element)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s cannot be used on a synchronized element. If you think you shall need to use the synchronized keyword for a specific use case, please post on the mailing list."); } } public void isInterface(TypeElement element, IsValid valid) { if (!annotationHelper.isInterface(element)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on an interface"); } } public void isTopLevel(TypeElement element, IsValid valid) { if (!annotationHelper.isTopLevel(element)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on a top level type"); } } public void doesNotExtendOtherInterfaces(TypeElement element, IsValid valid) { if (element.getInterfaces().size() > 0) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on an interface that does not extend other interfaces"); } } public void doesNotReturnPrimitive(ExecutableElement element, IsValid valid) { if (element.getReturnType().getKind().isPrimitive()) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s cannot return primitive"); } } public void doesNotReturnArray(ExecutableElement element, IsValid valid) { if (element.getReturnType().getKind() == TypeKind.ARRAY) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s cannot return array"); } } public void isNotPrivate(Element element, IsValid valid) { if (annotationHelper.isPrivate(element)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s cannot be used on a private element"); } } public void enclosingElementHasEBeanAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { Element enclosingElement = element.getEnclosingElement(); hasClassAnnotation(element, enclosingElement, validatedElements, EBean.class, valid); } public void enclosingElementHasEActivity(Element element, AnnotationElements validatedElements, IsValid valid) { Element enclosingElement = element.getEnclosingElement(); hasClassAnnotation(element, enclosingElement, validatedElements, EActivity.class, valid); } public void enclosingElementHasEActivityOrEFragment(Element element, AnnotationElements validatedElements, IsValid valid) { Element enclosingElement = element.getEnclosingElement(); @SuppressWarnings("unchecked") List<Class<? extends Annotation>> validAnnotations = asList(EActivity.class, EFragment.class); hasOneOfClassAnnotations(element, enclosingElement, validatedElements, validAnnotations, valid); } public void enclosingElementHasEFragment(Element element, AnnotationElements validatedElements, IsValid valid) { Element enclosingElement = element.getEnclosingElement(); hasClassAnnotation(element, enclosingElement, validatedElements, EFragment.class, valid); } public void hasEActivity(Element element, AnnotationElements validatedElements, IsValid valid) { hasClassAnnotation(element, element, validatedElements, EActivity.class, valid); } public void hasEActivityOrEFragment(Element element, AnnotationElements validatedElements, IsValid valid) { @SuppressWarnings("unchecked") List<Class<? extends Annotation>> validAnnotations = asList(EActivity.class, EFragment.class); hasOneOfClassAnnotations(element, element, validatedElements, validAnnotations, valid); } public void enclosingElementHasEnhancedViewSupportAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { Element enclosingElement = element.getEnclosingElement(); hasOneOfClassAnnotations(element, enclosingElement, validatedElements, VALID_ENHANCED_VIEW_SUPPORT_ANNOTATIONS, valid); } public void enclosingElementHasEnhancedComponentAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { Element enclosingElement = element.getEnclosingElement(); hasOneOfClassAnnotations(element, enclosingElement, validatedElements, VALID_ENHANCED_COMPONENT_ANNOTATIONS, valid); } private void hasClassAnnotation(Element reportElement, Element element, AnnotationElements validatedElements, Class<? extends Annotation> validAnnotation, IsValid valid) { ArrayList<Class<? extends Annotation>> validAnnotations = new ArrayList<Class<? extends Annotation>>(); validAnnotations.add(validAnnotation); hasOneOfClassAnnotations(reportElement, element, validatedElements, validAnnotations, valid); } private void hasOneOfClassAnnotations(Element reportElement, Element element, AnnotationElements validatedElements, List<Class<? extends Annotation>> validAnnotations, IsValid valid) { boolean foundAnnotation = false; for (Class<? extends Annotation> validAnnotation : validAnnotations) { if (element.getAnnotation(validAnnotation) != null) { Set<? extends Element> layoutAnnotatedElements = validatedElements .getRootAnnotatedElements(validAnnotation.getName()); /* * This is for the case where the element has the right * annotation, but that annotation was not validated. We do not * add any compile error (should already exist on the * annotation), but we still invalidate this element. */ if (!layoutAnnotatedElements.contains(element)) { valid.invalidate(); } foundAnnotation = true; break; } } if (!foundAnnotation) { valid.invalidate(); annotationHelper.printAnnotationError(reportElement, "%s can only be used in a class annotated with " + getFormattedValidEnhancedBeanAnnotationTypes(validAnnotations) + "."); } } private String getFormattedValidEnhancedBeanAnnotationTypes(List<Class<? extends Annotation>> annotations) { StringBuilder sb = new StringBuilder(); if (!annotations.isEmpty()) { sb.append(TargetAnnotationHelper.annotationName(annotations.get(0))); for (int i = 1; i < annotations.size(); i++) { sb.append(", "); sb.append(TargetAnnotationHelper.annotationName(annotations.get(i))); } } return sb.toString(); } public void hasViewByIdAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { String error = "can only be used with annotation"; elementHasAnnotation(ViewById.class, element, validatedElements, valid, error); } public void elementHasRestAnnotationOrEnclosingElementHasRestAnnotationAndElementHasMethodRestAnnotation( Element element, AnnotationElements validatedElements, IsValid valid) { String error = "can only be used in an interface annotated with"; elementHasAnnotation(Rest.class, element, validatedElements, valid, error); if (!valid.isValid()) { enclosingElementHasRestAnnotation(element, validatedElements, valid); elementHasMethodRestAnnotation(element, validatedElements, valid); } } public void elementHasMethodRestAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { String error = "can only be used on a method annotated with Rest methods."; elementHasAnnotationContainsIn(REST_ANNOTATION_CLASSES, element, validatedElements, valid, error); } public void enclosingElementHasRestAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { String error = "can only be used in an interface annotated with"; enclosingElementHasAnnotation(Rest.class, element, validatedElements, valid, error); } public void enclosingElementHasAnnotation(Class<? extends Annotation> annotation, Element element, AnnotationElements validatedElements, IsValid valid, String error) { Element enclosingElement = element.getEnclosingElement(); elementHasAnnotation(annotation, enclosingElement, validatedElements, valid, error); } public void elementHasAnnotation(Class<? extends Annotation> annotation, Element element, AnnotationElements validatedElements, IsValid valid, String error) { Set<? extends Element> layoutAnnotatedElements = validatedElements .getRootAnnotatedElements(annotation.getName()); if (!layoutAnnotatedElements.contains(element)) { valid.invalidate(); if (element.getAnnotation(annotation) == null) { annotationHelper.printAnnotationError(element, "%s " + error + " " + TargetAnnotationHelper.annotationName(annotation)); } } } public void elementHasAnnotationContainsIn(List<Class<? extends Annotation>> annotations, Element element, AnnotationElements validatedElements, IsValid valid, String error) { boolean isAnnoted = false; for (Class<? extends Annotation> annotation : annotations) { if (elementHasAnnotation(annotation, element, validatedElements)) { isAnnoted = true; break; } } if (!isAnnoted) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s " + error); } } public boolean elementHasAnnotation(Class<? extends Annotation> annotation, Element element, AnnotationElements validatedElements) { Set<? extends Element> layoutAnnotatedElements = validatedElements .getRootAnnotatedElements(annotation.getName()); return layoutAnnotatedElements.contains(element); } public void throwsOnlyRestClientException(ExecutableElement element, IsValid valid) { List<? extends TypeMirror> thrownTypes = element.getThrownTypes(); if (thrownTypes.size() > 0) { if (thrownTypes.size() > 1 || !thrownTypes.get(0).toString() .equals("org.springframework.web.client.RestClientException")) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated methods can only declare throwing a RestClientException"); } } } public void elementHasGetOrPostAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { if (!elementHasAnnotation(Get.class, element) && !elementHasAnnotation(Post.class, element)) { annotationHelper.printAnnotationError(element, "%s can only be used in an interface annotated with Get or Post annotation"); } } public void typeHasAnnotation(Class<? extends Annotation> annotation, Element element, IsValid valid) { TypeMirror elementType = element.asType(); typeHasAnnotation(annotation, elementType, element, valid); } public void typeHasAnnotation(Class<? extends Annotation> annotation, TypeMirror elementType, Element reportingElement, IsValid valid) { Element typeElement = annotationHelper.getTypeUtils().asElement(elementType); if (!elementHasAnnotationSafe(annotation, typeElement)) { valid.invalidate(); annotationHelper.printAnnotationError(reportingElement, "%s can only be used on an element annotated with " + TargetAnnotationHelper.annotationName(annotation)); } } public void typeOrTargetValueHasAnnotation(Class<? extends Annotation> annotation, Element element, IsValid valid) { DeclaredType targetAnnotationClassValue = annotationHelper.extractAnnotationClassParameter(element); if (targetAnnotationClassValue != null) { typeHasAnnotation(annotation, targetAnnotationClassValue, element, valid); if (!annotationHelper.getTypeUtils().isAssignable(targetAnnotationClassValue, element.asType())) { valid.invalidate(); annotationHelper.printAnnotationError(element, "The value of %s must be assignable into the annotated field"); } } else { typeHasAnnotation(annotation, element, valid); } } private boolean elementHasAnnotationSafe(Class<? extends Annotation> annotation, Element element) { List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors(); for (AnnotationMirror annotationMirror : annotationMirrors) { if (annotationMirror.getAnnotationType().toString().equals(annotation.getName())) { return true; } } return false; } private boolean elementHasAnnotation(Class<? extends Annotation> annotation, Element element) { return element.getAnnotation(annotation) != null; } public void elementHasRestAnnotation(Element element, AnnotationElements validatedElements, IsValid valid) { String error = "can only be used in an interface annotated with"; elementHasAnnotation(Rest.class, element, validatedElements, valid, error); } public void returnTypeNotGenericUnlessResponseEntity(ExecutableElement element, IsValid valid) { TypeMirror returnType = element.getReturnType(); TypeKind returnKind = returnType.getKind(); if (returnKind == TypeKind.DECLARED) { DeclaredType declaredReturnType = (DeclaredType) returnType; if (!declaredReturnType.toString().startsWith("org.springframework.http.ResponseEntity<") && declaredReturnType.getTypeArguments().size() > 0) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated methods cannot return parameterized types, except for ResponseEntity"); } } } public void hasHttpHeadersReturnType(ExecutableElement element, IsValid valid) { String returnType = element.getReturnType().toString(); if (!returnType.equals("org.springframework.http.HttpHeaders")) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated methods can only return a HttpHeaders, not " + returnType); } } public void hasSetOfHttpMethodReturnType(ExecutableElement element, IsValid valid) { TypeMirror returnType = element.getReturnType(); String returnTypeString = returnType.toString(); if (!returnTypeString.equals("java.util.Set<org.springframework.http.HttpMethod>")) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%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.invalidate(); annotationHelper.printAnnotationError(element, "%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.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated methods can only return a parameterized Set of HttpMethod, not " + typeArgument.toString()); } } } } public void urlVariableNamesExistInParameters(ExecutableElement element, List<String> variableNames, IsValid valid) { List<? extends VariableElement> parameters = element.getParameters(); List<String> parametersName = new ArrayList<String>(); for (VariableElement parameter : parameters) { parametersName.add(parameter.getSimpleName().toString()); } for (String variableName : variableNames) { if (!parametersName.contains(variableName)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated method has an url variable which name could not be found in the method parameters: " + variableName); return; } } } public void doesntThrowException(Element element, IsValid valid) { ExecutableElement executableElement = (ExecutableElement) element; if (executableElement.getThrownTypes().size() > 0) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated methods should not declare throwing any exception"); } } public void returnTypeIsVoidOrBoolean(ExecutableElement executableElement, IsValid valid) { TypeMirror returnType = executableElement.getReturnType(); TypeKind returnKind = returnType.getKind(); if (returnKind != TypeKind.BOOLEAN && returnKind != TypeKind.VOID && !returnType.toString().equals("java.lang.Boolean")) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with a boolean or a void return type"); } } public void returnTypeIsVoid(ExecutableElement executableElement, IsValid valid) { TypeMirror returnType = executableElement.getReturnType(); if (returnType.getKind() != TypeKind.VOID) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with a void return type"); } } public void zeroOrOneParameter(ExecutableElement executableElement, IsValid valid) { List<? extends VariableElement> parameters = executableElement.getParameters(); if (parameters.size() > 1) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with zero or one parameter, instead of " + parameters.size()); } } public void zeroParameter(ExecutableElement executableElement, IsValid valid) { List<? extends VariableElement> parameters = executableElement.getParameters(); if (parameters.size() > 0) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with zero parameter, instead of " + parameters.size()); } } public void zeroOrOneViewParameters(ExecutableElement executableElement, IsValid valid) { zeroOrOneSpecificParameter(executableElement, CanonicalNameConstants.VIEW, valid); } public void zeroOrOneMenuItemParameters(ExecutableElement executableElement, IsValid valid) { zeroOrOneSpecificParameter(executableElement, ANDROID_SHERLOCK_MENU_ITEM_QUALIFIED_NAMES, valid); } public void zeroOrOneSpecificParameter(ExecutableElement executableElement, String parameterTypeQualifiedName, IsValid valid) { zeroOrOneSpecificParameter(executableElement, Arrays.asList(parameterTypeQualifiedName), valid); } public void zeroOrOneSpecificParameter(ExecutableElement executableElement, List<String> parameterTypeQualifiedNames, IsValid valid) { zeroOrOneParameter(executableElement, valid); List<? extends VariableElement> parameters = executableElement.getParameters(); if (parameters.size() == 1) { VariableElement parameter = parameters.get(0); TypeMirror parameterType = parameter.asType(); if (!parameterTypeQualifiedNames.contains(parameterType.toString())) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with no parameter or a parameter of type " + parameterTypeQualifiedNames + ", not " + parameterType); } } } public void zeroOrOneBundleParameter(ExecutableElement executableElement, IsValid valid) { zeroOrOneSpecificParameter(executableElement, CanonicalNameConstants.BUNDLE, valid); } public void extendsActivity(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.ACTIVITY, valid); } public void extendsFragment(Element element, IsValid valid) { extendsOneOfTypes(element, ANDROID_FRAGMENT_QUALIFIED_NAMES, valid); } public void extendsService(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.SERVICE, valid); } public void extendsReceiver(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.BROADCAST_RECEIVER, valid); } public void extendsProvider(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.CONTENT_PROVIDER, valid); } public void extendsView(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.VIEW, valid); } public void extendsTextView(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.TEXT_VIEW, valid); } public void extendsViewGroup(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.VIEW_GROUP, valid); } public void extendsApplication(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.APPLICATION, valid); } public void extendsContext(Element element, IsValid valid) { extendsType(element, CanonicalNameConstants.CONTEXT, valid); } public void extendsOrmLiteDaoWithValidModelParameter(Element element, IsValid valid) { TypeMirror elementType = element.asType(); TypeMirror modelTypeMirror = annotationHelper.extractAnnotationParameter(element, "model"); TypeElement daoTypeElement = annotationHelper.typeElementFromQualifiedName(CanonicalNameConstants.DAO); if (daoTypeElement != null) { TypeMirror wildcardType = annotationHelper.getTypeUtils().getWildcardType(null, null); DeclaredType daoParameterizedType = annotationHelper.getTypeUtils().getDeclaredType(daoTypeElement, modelTypeMirror, wildcardType); // Checks that elementType extends Dao<ModelType, ?> if (!annotationHelper.isSubtype(elementType, daoParameterizedType)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on an element that extends " + daoParameterizedType.toString()); } } } public void hasASqlLiteOpenHelperParameterizedType(Element element, IsValid valid) { TypeMirror helperType = annotationHelper.extractAnnotationParameter(element, "helper"); TypeMirror openHelperType = annotationHelper .typeElementFromQualifiedName(CanonicalNameConstants.SQLLITE_OPEN_HELPER).asType(); if (!annotationHelper.isSubtype(helperType, openHelperType)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s helper() parameter must extend " + CanonicalNameConstants.SQLLITE_OPEN_HELPER); } } public void upperclassOfRegisteredApplication(Element element, AndroidManifest manifest, IsValid valid) { if (manifest.isLibraryProject()) { return; } String applicationClassName = manifest.getApplicationClassName(); if (applicationClassName != null) { if (applicationClassName.endsWith(GENERATION_SUFFIX)) { applicationClassName = applicationClassName.substring(0, applicationClassName.length() - GENERATION_SUFFIX.length()); } TypeMirror elementType = element.asType(); TypeMirror manifestType = annotationHelper.typeElementFromQualifiedName(applicationClassName).asType(); if (!annotationHelper.isSubtype(manifestType, elementType)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on an element that is an instance of the following class (or one of it's superclass): " + applicationClassName); } } else { valid.invalidate(); annotationHelper.printAnnotationError(element, "No application class is registered in the AndroidManifest.xml"); } } public void applicationRegistered(Element element, AndroidManifest manifest, IsValid valid) { if (manifest.isLibraryProject()) { return; } String applicationClassName = manifest.getApplicationClassName(); if (applicationClassName != null) { TypeElement typeElement = (TypeElement) element; String componentQualifiedName = typeElement.getQualifiedName().toString(); String generatedComponentQualifiedName = componentQualifiedName + ModelConstants.GENERATION_SUFFIX; if (!applicationClassName.equals(generatedComponentQualifiedName)) { if (applicationClassName.equals(componentQualifiedName)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "The AndroidManifest.xml file contains the original component, and not the AndroidAnnotations generated component. Please register " + generatedComponentQualifiedName + " instead of " + componentQualifiedName); } else { annotationHelper.printAnnotationWarning(element, "The component " + generatedComponentQualifiedName + " is not registered in the AndroidManifest.xml file."); } } } else { valid.invalidate(); annotationHelper.printAnnotationError(element, "No application class registered in the AndroidManifest.xml"); } } public void isSharedPreference(Element element, AnnotationElements validatedElements, IsValid valid) { TypeMirror type = element.asType(); /* * The type is not available yet because it has just been generated */ if (type instanceof ErrorType) { String elementTypeName = type.toString(); boolean sharedPrefValidatedInRound = false; if (elementTypeName.endsWith(GENERATION_SUFFIX)) { String prefTypeName = elementTypeName.substring(0, elementTypeName.length() - GENERATION_SUFFIX.length()); Set<? extends Element> sharedPrefElements = validatedElements .getRootAnnotatedElements(SharedPref.class.getName()); for (Element sharedPrefElement : sharedPrefElements) { TypeElement sharedPrefTypeElement = (TypeElement) sharedPrefElement; String sharedPrefQualifiedName = sharedPrefTypeElement.getQualifiedName().toString(); if (sharedPrefQualifiedName.endsWith(prefTypeName)) { sharedPrefValidatedInRound = true; break; } } } if (!sharedPrefValidatedInRound) { valid.invalidate(); } } else { extendsType(element, SharedPreferencesHelper.class.getName(), valid); } } public void extendsOneOfTypes(Element element, List<String> typeQualifiedNames, IsValid valid) { TypeMirror elementType = element.asType(); for (String typeQualifiedName : typeQualifiedNames) { TypeElement typeElement = annotationHelper.typeElementFromQualifiedName(typeQualifiedName); if (typeElement != null) { TypeMirror expectedType = typeElement.asType(); if (annotationHelper.isSubtype(elementType, expectedType)) { return; } } } valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on an element that extends one of the following classes: " + typeQualifiedNames); } public void extendsType(Element element, String typeQualifiedName, IsValid valid) { TypeMirror elementType = element.asType(); TypeElement typeElement = annotationHelper.typeElementFromQualifiedName(typeQualifiedName); if (typeElement != null) { TypeMirror expectedType = typeElement.asType(); if (!annotationHelper.isSubtype(elementType, expectedType)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on an element that extends " + typeQualifiedName); } } } public void hasOneOrTwoParametersAndFirstIsBoolean(ExecutableElement executableElement, IsValid valid) { List<? extends VariableElement> parameters = executableElement.getParameters(); if (parameters.size() < 1 || parameters.size() > 2) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with 1 or 2 parameter, instead of " + parameters.size()); } else { VariableElement firstParameter = parameters.get(0); TypeKind parameterKind = firstParameter.asType().getKind(); if (parameterKind != TypeKind.BOOLEAN && !firstParameter.toString().equals("java.lang.Boolean")) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "the first parameter should be a boolean"); } } } public void allowedType(Element element, IsValid valid, TypeMirror fieldTypeMirror, List<String> allowedTypes) { String qualifiedName = fieldTypeMirror.toString(); if (!allowedTypes.contains(qualifiedName)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on a field which is a " + allowedTypes.toString() + ", not " + qualifiedName); } } public void hasRoboGuiceJars(Element element, IsValid valid) { Elements elementUtils = annotationHelper.getElementUtils(); if (elementUtils.getTypeElement(CanonicalNameConstants.INJECTOR_PROVIDER) == null) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Could not find the RoboGuice framework in the classpath, the following class is missing: " + CanonicalNameConstants.INJECTOR_PROVIDER); } if (elementUtils.getTypeElement(RoboGuiceConstants.ROBOGUICE_APPLICATION_CLASS) == null) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Could not find the RoboApplication class in the classpath, are you using RoboGuice 1.1.1 ?"); } try { if (elementUtils.getTypeElement(CanonicalNameConstants.INJECTOR) == null) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Could not find the Guice framework in the classpath, the following class is missing: " + CanonicalNameConstants.INJECTOR); } } catch (RuntimeException e) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Could not find the Guice framework in the classpath, the following class is missing: " + CanonicalNameConstants.INJECTOR); } } public void hasSpringAndroidJars(Element element, IsValid valid) { Elements elementUtils = annotationHelper.getElementUtils(); if (elementUtils.getTypeElement(CanonicalNameConstants.REST_TEMPLATE) == null) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Could not find the SpringAndroid framework in the classpath, the following class is missing: " + CanonicalNameConstants.REST_TEMPLATE); } } public void hasOrmLiteJars(Element element, IsValid valid) { Elements elementUtils = annotationHelper.getElementUtils(); if (elementUtils.getTypeElement(CanonicalNameConstants.DAO) == null) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Could not find the OrmLite framework in the classpath, the following class is missing: " + CanonicalNameConstants.DAO); } } public void androidService(AndroidSystemServices androidSystemServices, Element element, IsValid valid) { TypeMirror serviceType = element.asType(); if (!androidSystemServices.contains(serviceType)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "Unknown service type: " + serviceType.toString()); } } public void hasOneMotionEventOrTwoMotionEventViewParameters(ExecutableElement executableElement, IsValid valid) { List<? extends VariableElement> parameters = executableElement.getParameters(); if (parameters.size() < 1 || parameters.size() > 2) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "%s can only be used on a method with 1 (MotionEvent) or 2 (MotionEvent, View) parameters, instead of " + parameters.size()); } else { VariableElement firstParameter = parameters.get(0); String firstParameterType = firstParameter.asType().toString(); if (!firstParameterType.equals(CanonicalNameConstants.MOTION_EVENT)) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "the first parameter must be a " + CanonicalNameConstants.MOTION_EVENT + ", not a " + firstParameterType); } if (parameters.size() == 2) { VariableElement secondParameter = parameters.get(1); String secondParameterType = secondParameter.asType().toString(); if (!secondParameterType.equals(CanonicalNameConstants.VIEW)) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "the second parameter must be a " + CanonicalNameConstants.VIEW + ", not a " + secondParameterType); } } } } public void hasOneOrTwoParametersAndFirstIsDb(ExecutableElement executableElement, IsValid valid) { List<? extends VariableElement> parameters = executableElement.getParameters(); if (parameters.size() < 1) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "There should be at least 1 parameter: a " + CanonicalNameConstants.SQLITE_DATABASE); } else { VariableElement firstParameter = parameters.get(0); String firstParameterType = firstParameter.asType().toString(); if (!firstParameterType.equals(CanonicalNameConstants.SQLITE_DATABASE)) { valid.invalidate(); annotationHelper.printAnnotationError(executableElement, "the first parameter must be a " + CanonicalNameConstants.SQLITE_DATABASE + ", not a " + firstParameterType); } } } public void isDeclaredType(Element element, IsValid valid, TypeMirror uiFieldTypeMirror) { if (!(uiFieldTypeMirror instanceof DeclaredType)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s can only be used on a field which is a declared type"); } } public boolean isPrefMethod(Element element) { if (!element.getKind().equals(ElementKind.METHOD)) { annotationHelper.printError(element, "Only methods are allowed in an " + annotationHelper.annotationName() + " annotated interface"); } else { ExecutableElement executableElement = (ExecutableElement) element; String methodName = executableElement.getSimpleName().toString(); if (executableElement.getParameters().size() > 0) { annotationHelper.printError(element, "Method " + methodName + " should have no parameters in an " + annotationHelper.annotationName() + " annotated interface"); } else { String returnType = executableElement.getReturnType().toString(); if (!VALID_PREF_RETURN_TYPES.contains(returnType)) { annotationHelper.printError(element, "Method " + methodName + " should only return preference simple types in an " + annotationHelper.annotationName() + " annotated interface"); } else { if (INVALID_PREF_METHOD_NAMES.contains(methodName)) { annotationHelper.printError(element, "The method name " + methodName + " is forbidden in an " + annotationHelper.annotationName() + " annotated interface"); } else { return true; } } } } return false; } public void hasCorrectDefaultAnnotation(ExecutableElement method) { checkDefaultAnnotation(method, DefaultBoolean.class, "boolean", new TypeKindAnnotationCondition(TypeKind.BOOLEAN)); checkDefaultAnnotation(method, DefaultFloat.class, "float", new TypeKindAnnotationCondition(TypeKind.FLOAT)); checkDefaultAnnotation(method, DefaultInt.class, "int", new TypeKindAnnotationCondition(TypeKind.INT)); checkDefaultAnnotation(method, DefaultLong.class, "long", new TypeKindAnnotationCondition(TypeKind.LONG)); checkDefaultAnnotation(method, DefaultString.class, "String", new DefaultAnnotationCondition() { @Override public boolean correctReturnType(TypeMirror returnType) { return returnType.toString().equals(CanonicalNameConstants.STRING); } }); } private interface DefaultAnnotationCondition { boolean correctReturnType(TypeMirror returnType); } private class TypeKindAnnotationCondition implements DefaultAnnotationCondition { private final TypeKind typeKind; public TypeKindAnnotationCondition(TypeKind typeKind) { this.typeKind = typeKind; } @Override public boolean correctReturnType(TypeMirror returnType) { return returnType.getKind() == typeKind; } } private <T extends Annotation> void checkDefaultAnnotation(ExecutableElement method, Class<T> annotationClass, String expectedReturnType, DefaultAnnotationCondition condition) { T defaultAnnotation = method.getAnnotation(annotationClass); if (defaultAnnotation != null) { if (!condition.correctReturnType(method.getReturnType())) { annotationHelper.printAnnotationError(method, annotationClass, TargetAnnotationHelper.annotationName(annotationClass) + " can only be used on a method that returns a " + expectedReturnType); } } } @SuppressWarnings("unchecked") private static final List<Class<? extends Annotation>> REST_ANNOTATION_CLASSES = Arrays.asList(Get.class, Head.class, Options.class, Post.class, Put.class, Delete.class); public void unannotatedMethodReturnsRestTemplate(TypeElement typeElement, IsValid valid) { List<? extends Element> enclosedElements = typeElement.getEnclosedElements(); boolean foundGetRestTemplateMethod = false; boolean foundSetRestTemplateMethod = false; boolean foundSetRootUrlMethod = false; for (Element enclosedElement : enclosedElements) { if (enclosedElement.getKind() != ElementKind.METHOD) { valid.invalidate(); annotationHelper.printError(enclosedElement, "Only methods are allowed in a " + TargetAnnotationHelper.annotationName(Rest.class) + " 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(); if (returnType.toString().equals(CanonicalNameConstants.REST_TEMPLATE)) { if (executableElement.getParameters().size() > 0) { valid.invalidate(); annotationHelper.printError(enclosedElement, "The method returning a RestTemplate should not declare any parameter in a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface"); } else { if (foundGetRestTemplateMethod) { valid.invalidate(); annotationHelper.printError(enclosedElement, "Only one method should declare returning a RestTemplate in a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface"); } else { foundGetRestTemplateMethod = true; } } } 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(CanonicalNameConstants.REST_TEMPLATE)) { if (!foundSetRestTemplateMethod) { foundSetRestTemplateMethod = true; } else { valid.invalidate(); annotationHelper.printError(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 { valid.invalidate(); annotationHelper.printError(enclosedElement, "The method to set a RestTemplate should have only one RestTemplate parameter on a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface"); } } else { valid.invalidate(); annotationHelper.printError(enclosedElement, "The method to set a RestTemplate should have only one RestTemplate parameter on a " + TargetAnnotationHelper.annotationName(Rest.class) + " annotated interface"); } } else { valid.invalidate(); annotationHelper.printError(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 notAlreadyValidated(Element element, AnnotationElements validatedElements, IsValid valid) { if (validatedElements.getAllElements().contains(element)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "%s annotated element cannot be used with the other annotations used on this element."); } } public void hasEmptyOrContextConstructor(Element element, IsValid valid) { List<ExecutableElement> constructors = ElementFilter.constructorsIn(element.getEnclosedElements()); if (constructors.size() == 1) { ExecutableElement constructor = constructors.get(0); if (!annotationHelper.isPrivate(constructor)) { if (constructor.getParameters().size() > 1) { annotationHelper.printAnnotationError(element, "%s annotated element should have a constructor with one parameter max, of type " + CanonicalNameConstants.CONTEXT); valid.invalidate(); } else if (constructor.getParameters().size() == 1) { VariableElement parameter = constructor.getParameters().get(0); if (!parameter.asType().toString().equals(CanonicalNameConstants.CONTEXT)) { annotationHelper.printAnnotationError(element, "%s annotated element should have a constructor with one parameter max, of type " + CanonicalNameConstants.CONTEXT); valid.invalidate(); } } } else { annotationHelper.printAnnotationError(element, "%s annotated element should not have a private constructor"); valid.invalidate(); } } else { annotationHelper.printAnnotationError(element, "%s annotated element should have only one constructor"); valid.invalidate(); } } public void hasEmptyConstructor(Element element, IsValid valid) { List<ExecutableElement> constructors = ElementFilter.constructorsIn(element.getEnclosedElements()); if (constructors.size() == 1) { ExecutableElement constructor = constructors.get(0); if (!annotationHelper.isPrivate(constructor)) { if (constructor.getParameters().size() != 0) { annotationHelper.printAnnotationError(element, "%s annotated element should have an empty constructor"); valid.invalidate(); } } else { annotationHelper.printAnnotationError(element, "%s annotated element should not have a private constructor"); valid.invalidate(); } } else { annotationHelper.printAnnotationError(element, "%s annotated element should have only one constructor"); valid.invalidate(); } } public void hasValidLogLevel(Element element, IsValid isValid) { Trace annotation = element.getAnnotation(Trace.class); Integer level = annotation.level(); if (!VALID_LOG_LEVELS.contains(level)) { annotationHelper.printError(element, "Unrecognized log level."); isValid.invalidate(); } } public void canBeSavedAsInstanceState(Element element, IsValid isValid) { String typeString = element.asType().toString(); if (!isKnowInstanceStateType(typeString)) { if (element.asType() instanceof DeclaredType) { DeclaredType declaredType = (DeclaredType) element.asType(); typeString = declaredType.asElement().toString(); } else if (element.asType() instanceof ArrayType) { ArrayType arrayType = (ArrayType) element.asType(); TypeMirror componentType = arrayType.getComponentType(); if (componentType instanceof DeclaredType) { DeclaredType declaredType = (DeclaredType) componentType; typeString = declaredType.asElement().toString(); } else { typeString = componentType.toString(); } } else { typeString = element.asType().toString(); } TypeElement elementType = annotationHelper.typeElementFromQualifiedName(typeString); if (elementType == null) { elementType = getArrayEnclosingType(typeString); if (elementType == null) { annotationHelper.printAnnotationError(element, "Unrecognized type. Please let your attribute be primitive or implement Serializable or Parcelable"); isValid.invalidate(); } } if (elementType != null) { TypeElement parcelableType = annotationHelper.typeElementFromQualifiedName("android.os.Parcelable"); TypeElement serializableType = annotationHelper .typeElementFromQualifiedName("java.io.Serializable"); if (!annotationHelper.isSubtype(elementType, parcelableType) && !annotationHelper.isSubtype(elementType, serializableType)) { annotationHelper.printAnnotationError(element, "Unrecognized type. Please let your attribute be primitive or implement Serializable or Parcelable"); isValid.invalidate(); } } } } private TypeElement getArrayEnclosingType(String typeString) { typeString = typeString.replace("[]", ""); return annotationHelper.typeElementFromQualifiedName(typeString); } private boolean isKnowInstanceStateType(String type) { return InstanceStateProcessor.methodSuffixNameByTypeName.containsKey(type); } public void componentRegistered(Element element, AndroidManifest androidManifest, IsValid valid) { componentRegistered(element, androidManifest, true, valid); } public void componentRegistered(Element element, AndroidManifest androidManifest, boolean printWarning, IsValid valid) { TypeElement typeElement = (TypeElement) element; if (typeElement.getModifiers().contains(Modifier.ABSTRACT)) { return; } if (androidManifest.isLibraryProject()) { return; } String componentQualifiedName = typeElement.getQualifiedName().toString(); String generatedComponentQualifiedName = componentQualifiedName + ModelConstants.GENERATION_SUFFIX; List<String> componentQualifiedNames = androidManifest.getComponentQualifiedNames(); if (!componentQualifiedNames.contains(generatedComponentQualifiedName)) { String simpleName = typeElement.getSimpleName().toString(); String generatedSimpleName = simpleName + ModelConstants.GENERATION_SUFFIX; if (componentQualifiedNames.contains(componentQualifiedName)) { valid.invalidate(); annotationHelper.printAnnotationError(element, "The AndroidManifest.xml file contains the original component, and not the AndroidAnnotations generated component. Please register " + generatedSimpleName + " instead of " + simpleName); } else { if (printWarning) { annotationHelper.printAnnotationWarning(element, "The component " + generatedSimpleName + " is not registered in the AndroidManifest.xml file."); } } } } 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); } } } }