/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.validator.ap.checks;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.lang.model.util.Types;
import javax.validation.GroupSequence;
import org.hibernate.validator.ap.util.AnnotationApiHelper;
import org.hibernate.validator.ap.util.CollectionHelper;
import org.hibernate.validator.group.DefaultGroupSequenceProvider;
/**
* Checks that the {@link org.hibernate.validator.group.GroupSequenceProvider} annotation definition is valid.
* <p>
* This check ensure that :
* <ul>
* <li>The annotation is not defined on an interface.</li>
* <li>The annotation defines an implementation class of {@link DefaultGroupSequenceProvider}, not an interface or an abstract class.</li>
* <li>The annotation defines a class with a public default constructor.</li>
* <li>The annotation defines a default group sequence provider class for a (super-)type of the annotated class.</li>
* <li>The class hosting the annotation is not already annotated with {@linkplain GroupSequence @GroupSequence}.</li>
* </ul>
* </p>
*
* @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI
*/
public class GroupSequenceProviderCheck extends AbstractConstraintCheck {
private final Types typeUtils;
private final AnnotationApiHelper annotationApiHelper;
private final TypeMirror defaultGroupSequenceProviderType;
public GroupSequenceProviderCheck(AnnotationApiHelper annotationApiHelper, Types typeUtils) {
this.typeUtils = typeUtils;
this.annotationApiHelper = annotationApiHelper;
this.defaultGroupSequenceProviderType = annotationApiHelper.getMirrorForType( DefaultGroupSequenceProvider.class );
}
@Override
public Set<ConstraintCheckError> checkNonAnnotationType(TypeElement element, AnnotationMirror annotation) {
Set<ConstraintCheckError> errors = CollectionHelper.newHashSet();
errors.addAll( checkHostingElement( element, annotation ) );
errors.addAll( checkAnnotationValue( element, annotation ) );
return errors;
}
private Set<ConstraintCheckError> checkHostingElement(TypeElement element, AnnotationMirror annotation) {
if ( !element.getKind().isClass() ) {
return CollectionHelper.asSet(
new ConstraintCheckError(
element, annotation, "GROUP_SEQUENCE_PROVIDER_ANNOTATION_MUST_BE_DEFINED_ON_A_CLASS"
)
);
}
//this error should be raised only if the GroupSequenceProvider annotations is on a class
if ( element.getAnnotation( GroupSequence.class ) != null ) {
return CollectionHelper.asSet(
new ConstraintCheckError(
element,
annotation,
"GROUP_SEQUENCE_PROVIDER_ANNOTATION_NOT_ALLOWED_ON_CLASS_WITH_GROUP_SEQUENCE_ANNOTATION"
)
);
}
return Collections.emptySet();
}
private Set<ConstraintCheckError> checkAnnotationValue(TypeElement element, AnnotationMirror annotation) {
Set<ConstraintCheckError> errors = CollectionHelper.newHashSet();
AnnotationValue value = annotationApiHelper.getAnnotationValue( annotation, "value" );
TypeMirror valueType = (TypeMirror) value.getValue();
TypeElement valueElement = (TypeElement) typeUtils.asElement( valueType );
if ( valueElement.getKind().isInterface() || valueElement.getModifiers().contains( Modifier.ABSTRACT ) ) {
errors.add(
new ConstraintCheckError(
element,
annotation,
"GROUP_SEQUENCE_PROVIDER_ANNOTATION_VALUE_MUST_BE_AN_IMPLEMENTATION_CLASS"
)
);
}
else {
//the TypeElement hosting the annotation is a concrete implementation of the DefaultGroupSequenceProvider
//interface. In that case, we need to check that it has a public default constructor.
if ( !hasPublicDefaultConstructor( valueElement ) ) {
errors.add(
new ConstraintCheckError(
element,
annotation,
"GROUP_SEQUENCE_PROVIDER_ANNOTATION_VALUE_CLASS_MUST_HAVE_DEFAULT_CONSTRUCTOR",
valueType
)
);
}
}
TypeMirror genericProviderType = retrieveGenericProviderType( valueType );
if ( !typeUtils.isSubtype( element.asType(), genericProviderType ) ) {
errors.add(
new ConstraintCheckError(
element,
annotation,
"GROUP_SEQUENCE_PROVIDER_ANNOTATION_VALUE_DEFINED_PROVIDER_CLASS_WITH_WRONG_TYPE",
genericProviderType,
element.asType()
)
);
}
return errors;
}
/**
* Checks that the given {@code TypeElement} has a public
* default constructor.
*
* @param element The {@code TypeElement} to check.
*
* @return True if the given {@code TypeElement} has a public default constructor, false otherwise
*/
private boolean hasPublicDefaultConstructor(TypeElement element) {
return element.accept(
new ElementKindVisitor6<Boolean, Void>( Boolean.FALSE ) {
@Override
public Boolean visitTypeAsClass(TypeElement typeElement, Void aVoid) {
List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
for ( Element enclosedElement : enclosedElements ) {
if ( enclosedElement.accept( this, aVoid ) ) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
@Override
public Boolean visitExecutableAsConstructor(ExecutableElement constructorElement, Void aVoid) {
if ( constructorElement.getModifiers().contains( Modifier.PUBLIC )
&& constructorElement.getParameters().isEmpty() ) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}, null
);
}
/**
* Retrieves the default group sequence provider generic type defined by the given {@code TypeMirror}.
*
* @param typeMirror The {@code TypeMirror} instance.
*
* @return The generic type or {@code null} if the given type doesn't implement the {@link DefaultGroupSequenceProvider} interface.
*/
private TypeMirror retrieveGenericProviderType(TypeMirror typeMirror) {
return typeMirror.accept(
new SimpleTypeVisitor6<TypeMirror, Void>() {
@Override
public TypeMirror visitDeclared(DeclaredType declaredType, Void aVoid) {
TypeMirror eraseType = typeUtils.erasure( declaredType );
if ( typeUtils.isSameType( eraseType, defaultGroupSequenceProviderType ) ) {
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if ( !typeArguments.isEmpty() ) {
return typeArguments.get( 0 );
}
return null;
}
List<? extends TypeMirror> superTypes = typeUtils.directSupertypes( declaredType );
for ( TypeMirror superType : superTypes ) {
TypeMirror genericProviderType = superType.accept( this, aVoid );
if ( genericProviderType != null ) {
return genericProviderType;
}
}
return null;
}
}, null
);
}
}