/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* 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.mapstruct.ap.internal.model.source.selector;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.prism.NamedPrism;
import org.mapstruct.ap.internal.prism.QualifierPrism;
/**
* This selector selects a best match based on qualifier annotations.
* <p>
* A method is said to be marked with a qualifier annotation if the class in which it resides is annotated with a
* qualifier annotation or if the method itself is annotated with a qualifier annotation or both.
* <p>
* Rules:
* <ol>
* <li>If no qualifiers are requested in the selection criteria, then only candidate methods without any qualifier
* annotations remain in the list of potential candidates</li>
* <li>If multiple qualifiers (qualifedBy) are specified, then all of them need to be present at a candidate for it to
* match.</li>
* <li>If no candidate matches the required qualifiers, then all candidates are returned.</li>
* </ol>
*
* @author Sjaak Derksen
*/
public class QualifierSelector implements MethodSelector {
private final Types typeUtils;
private final TypeMirror namedAnnotationTypeMirror;
public QualifierSelector( Types typeUtils, Elements elementUtils ) {
this.typeUtils = typeUtils;
namedAnnotationTypeMirror = elementUtils.getTypeElement( "org.mapstruct.Named" ).asType();
}
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
int numberOfQualifiersToMatch = 0;
// Define some local collections and make sure that they are defined.
List<TypeMirror> qualifierTypes = new ArrayList<TypeMirror>();
if ( criteria.getQualifiers() != null ) {
qualifierTypes.addAll( criteria.getQualifiers() );
numberOfQualifiersToMatch += criteria.getQualifiers().size();
}
List<String> qualfiedByNames = new ArrayList<String>();
if ( criteria.getQualifiedByNames() != null ) {
qualfiedByNames.addAll( criteria.getQualifiedByNames() );
numberOfQualifiersToMatch += criteria.getQualifiedByNames().size();
}
// add the mapstruct @Named annotation as annotation to look for
if ( !qualfiedByNames.isEmpty() ) {
qualifierTypes.add( namedAnnotationTypeMirror );
}
// Check there are qualfiers for this mapping: Mapping#qualifier or Mapping#qualfiedByName
if ( qualifierTypes.isEmpty() ) {
// When no qualifiers, disqualify all methods marked with a qualifier by removing them from the candidates
List<SelectedMethod<T>> nonQualiferAnnotatedMethods = new ArrayList<SelectedMethod<T>>( methods.size() );
for ( SelectedMethod<T> candidate : methods ) {
if ( candidate.getMethod() instanceof SourceMethod ) {
Set<AnnotationMirror> qualifierAnnotations = getQualifierAnnotationMirrors( candidate.getMethod() );
if ( qualifierAnnotations.isEmpty() ) {
nonQualiferAnnotatedMethods.add( candidate );
}
}
else {
nonQualiferAnnotatedMethods.add( candidate );
}
}
return nonQualiferAnnotatedMethods;
}
else {
// Check all methods marked with qualfier (or methods in Mappers marked wiht a qualfier) for matches.
List<SelectedMethod<T>> matches = new ArrayList<SelectedMethod<T>>( methods.size() );
for ( SelectedMethod<T> candidate : methods ) {
if ( !( candidate.getMethod() instanceof SourceMethod ) ) {
continue;
}
// retrieve annotations
Set<AnnotationMirror> qualifierAnnotationMirrors =
getQualifierAnnotationMirrors( candidate.getMethod() );
// now count if all qualifiers are matched
int matchingQualifierCounter = 0;
for ( AnnotationMirror qualifierAnnotationMirror : qualifierAnnotationMirrors ) {
for ( TypeMirror qualifierType : qualifierTypes ) {
// get the type of the annotation mirror.
DeclaredType qualifierAnnotationType = qualifierAnnotationMirror.getAnnotationType();
if ( typeUtils.isSameType( qualifierType, qualifierAnnotationType ) ) {
// Match! we have an annotation which has the @Qualifer marker ( could be @Named as well )
if ( typeUtils.isSameType( qualifierAnnotationType, namedAnnotationTypeMirror ) ) {
// Match! its an @Named, so do the additional check on name.
NamedPrism namedPrism = NamedPrism.getInstance( qualifierAnnotationMirror );
if ( namedPrism.value() != null && qualfiedByNames.contains( namedPrism.value() ) ) {
// Match! its an @Name and the value matches as well. Oh boy.
matchingQualifierCounter++;
}
}
else {
// Match! its a self declared qualifer annoation (marked with @Qualifier)
matchingQualifierCounter++;
}
break;
}
}
}
if ( matchingQualifierCounter == numberOfQualifiersToMatch ) {
// Only if all qualifiers are matched with a qualifying annotation, add candidate
matches.add( candidate );
}
}
if ( !matches.isEmpty() ) {
return matches;
}
else {
return methods;
}
}
}
private Set<AnnotationMirror> getQualifierAnnotationMirrors( Method candidate ) {
// retrieve annotations
Set<AnnotationMirror> qualiferAnnotations = new HashSet<AnnotationMirror>();
// first from the method itself
SourceMethod candidateSM = (SourceMethod) candidate;
List<? extends AnnotationMirror> methodAnnotations = candidateSM.getExecutable().getAnnotationMirrors();
for ( AnnotationMirror methodAnnotation : methodAnnotations ) {
addOnlyWhenQualifier( qualiferAnnotations, methodAnnotation );
}
// then from the mapper (if declared)
Type mapper = candidate.getDeclaringMapper();
if ( mapper != null ) {
List<? extends AnnotationMirror> mapperAnnotations = mapper.getTypeElement().getAnnotationMirrors();
for ( AnnotationMirror mapperAnnotation : mapperAnnotations ) {
addOnlyWhenQualifier( qualiferAnnotations, mapperAnnotation );
}
}
return qualiferAnnotations;
}
private void addOnlyWhenQualifier( Set<AnnotationMirror> annotationSet, AnnotationMirror candidate ) {
// only add the candidate annotation when the candidate itself has the annotation 'Qualifier'
if ( QualifierPrism.getInstanceOn( candidate.getAnnotationType().asElement() ) != null ) {
annotationSet.add( candidate );
}
}
}