1 /* 2 * Copyright (C) 2016 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.validation; 18 19 import static com.google.auto.common.MoreElements.isAnnotationPresent; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 23 import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; 24 import static java.util.stream.Collectors.joining; 25 26 import com.google.common.collect.ImmutableMap; 27 import com.google.common.collect.ImmutableSet; 28 import dagger.internal.codegen.base.ClearableCache; 29 import java.lang.annotation.Annotation; 30 import java.util.HashMap; 31 import java.util.Map; 32 import javax.inject.Inject; 33 import javax.inject.Singleton; 34 import javax.lang.model.element.ExecutableElement; 35 36 /** Validates any binding method. */ 37 @Singleton 38 public final class AnyBindingMethodValidator implements ClearableCache { 39 private final ImmutableMap<Class<? extends Annotation>, BindingMethodValidator> validators; 40 private final Map<ExecutableElement, ValidationReport<ExecutableElement>> reports = 41 new HashMap<>(); 42 43 @Inject AnyBindingMethodValidator( ImmutableMap<Class<? extends Annotation>, BindingMethodValidator> validators)44 AnyBindingMethodValidator( 45 ImmutableMap<Class<? extends Annotation>, BindingMethodValidator> validators) { 46 this.validators = validators; 47 } 48 49 @Override clearCache()50 public void clearCache() { 51 reports.clear(); 52 } 53 54 /** Returns the binding method annotations considered by this validator. */ methodAnnotations()55 ImmutableSet<Class<? extends Annotation>> methodAnnotations() { 56 return validators.keySet(); 57 } 58 59 /** 60 * Returns {@code true} if {@code method} is annotated with at least one of {@link 61 * #methodAnnotations()}. 62 */ isBindingMethod(ExecutableElement method)63 boolean isBindingMethod(ExecutableElement method) { 64 return isAnyAnnotationPresent(method, methodAnnotations()); 65 } 66 67 /** 68 * Returns a validation report for a method. 69 * 70 * <ul> 71 * <li>Reports an error if {@code method} is annotated with more than one {@linkplain 72 * #methodAnnotations() binding method annotation}. 73 * <li>Validates {@code method} with the {@link BindingMethodValidator} for the single 74 * {@linkplain #methodAnnotations() binding method annotation}. 75 * </ul> 76 * 77 * @throws IllegalArgumentException if {@code method} is not annotated by any {@linkplain 78 * #methodAnnotations() binding method annotation} 79 */ validate(ExecutableElement method)80 ValidationReport<ExecutableElement> validate(ExecutableElement method) { 81 return reentrantComputeIfAbsent(reports, method, this::validateUncached); 82 } 83 84 /** 85 * Returns {@code true} if {@code method} was already {@linkplain #validate(ExecutableElement) 86 * validated}. 87 */ wasAlreadyValidated(ExecutableElement method)88 boolean wasAlreadyValidated(ExecutableElement method) { 89 return reports.containsKey(method); 90 } 91 validateUncached(ExecutableElement method)92 private ValidationReport<ExecutableElement> validateUncached(ExecutableElement method) { 93 ValidationReport.Builder<ExecutableElement> report = ValidationReport.about(method); 94 ImmutableSet<? extends Class<? extends Annotation>> bindingMethodAnnotations = 95 methodAnnotations() 96 .stream() 97 .filter(annotation -> isAnnotationPresent(method, annotation)) 98 .collect(toImmutableSet()); 99 switch (bindingMethodAnnotations.size()) { 100 case 0: 101 throw new IllegalArgumentException( 102 String.format("%s has no binding method annotation", method)); 103 104 case 1: 105 report.addSubreport( 106 validators.get(getOnlyElement(bindingMethodAnnotations)).validate(method)); 107 break; 108 109 default: 110 report.addError( 111 String.format( 112 "%s is annotated with more than one of (%s)", 113 method.getSimpleName(), 114 methodAnnotations().stream().map(Class::getCanonicalName).collect(joining(", "))), 115 method); 116 break; 117 } 118 return report.build(); 119 } 120 } 121