1 /* 2 * Copyright (C) 2014 Google, Inc. 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 package dagger.internal.codegen; 17 18 import com.google.auto.common.MoreElements; 19 import com.google.auto.common.Visibility; 20 import com.google.common.base.Function; 21 import com.google.common.base.Joiner; 22 import com.google.common.base.Predicate; 23 import com.google.common.collect.ArrayListMultimap; 24 import com.google.common.collect.FluentIterable; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableSet; 27 import com.google.common.collect.ListMultimap; 28 import com.google.common.collect.Sets; 29 import dagger.Module; 30 import dagger.producers.ProducerModule; 31 import java.lang.annotation.Annotation; 32 import java.util.Collection; 33 import java.util.List; 34 import java.util.Map.Entry; 35 import java.util.Set; 36 import javax.lang.model.element.AnnotationMirror; 37 import javax.lang.model.element.Element; 38 import javax.lang.model.element.ElementKind; 39 import javax.lang.model.element.ExecutableElement; 40 import javax.lang.model.element.TypeElement; 41 import javax.lang.model.type.DeclaredType; 42 import javax.lang.model.type.TypeMirror; 43 import javax.lang.model.util.ElementFilter; 44 import javax.lang.model.util.Elements; 45 import javax.lang.model.util.SimpleTypeVisitor6; 46 import javax.lang.model.util.Types; 47 48 import static com.google.auto.common.MoreElements.getAnnotationMirror; 49 import static com.google.auto.common.MoreElements.isAnnotationPresent; 50 import static com.google.auto.common.Visibility.PRIVATE; 51 import static com.google.auto.common.Visibility.PUBLIC; 52 import static com.google.auto.common.Visibility.effectiveVisibilityOfElement; 53 import static com.google.common.collect.Iterables.any; 54 import static dagger.internal.codegen.ConfigurationAnnotations.getModuleIncludes; 55 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME; 56 import static dagger.internal.codegen.ErrorMessages.METHOD_OVERRIDES_PROVIDES_METHOD; 57 import static dagger.internal.codegen.ErrorMessages.MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT; 58 import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_OVERRIDES_ANOTHER; 59 import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULES_MUST_NOT_BE_ABSTRACT; 60 import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS; 61 import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED; 62 import static javax.lang.model.element.Modifier.ABSTRACT; 63 64 /** 65 * A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s. 66 * 67 * @author Gregory Kick 68 * @since 2.0 69 */ 70 final class ModuleValidator { 71 private final Types types; 72 private final Elements elements; 73 private final Class<? extends Annotation> moduleClass; 74 private final ImmutableList<Class<? extends Annotation>> includedModuleClasses; 75 private final Class<? extends Annotation> methodClass; 76 private final MethodSignatureFormatter methodSignatureFormatter; 77 ModuleValidator( Types types, Elements elements, MethodSignatureFormatter methodSignatureFormatter, Class<? extends Annotation> moduleClass, ImmutableList<Class<? extends Annotation>> includedModuleClasses, Class<? extends Annotation> methodClass)78 ModuleValidator( 79 Types types, 80 Elements elements, 81 MethodSignatureFormatter methodSignatureFormatter, 82 Class<? extends Annotation> moduleClass, 83 ImmutableList<Class<? extends Annotation>> includedModuleClasses, 84 Class<? extends Annotation> methodClass) { 85 this.types = types; 86 this.elements = elements; 87 this.moduleClass = moduleClass; 88 this.includedModuleClasses = includedModuleClasses; 89 this.methodClass = methodClass; 90 this.methodSignatureFormatter = methodSignatureFormatter; 91 } 92 validate(final TypeElement subject)93 ValidationReport<TypeElement> validate(final TypeElement subject) { 94 final ValidationReport.Builder<TypeElement> builder = ValidationReport.about(subject); 95 96 List<ExecutableElement> moduleMethods = ElementFilter.methodsIn(subject.getEnclosedElements()); 97 ListMultimap<String, ExecutableElement> allMethodsByName = ArrayListMultimap.create(); 98 ListMultimap<String, ExecutableElement> bindingMethodsByName = ArrayListMultimap.create(); 99 for (ExecutableElement moduleMethod : moduleMethods) { 100 if (isAnnotationPresent(moduleMethod, methodClass)) { 101 bindingMethodsByName.put(moduleMethod.getSimpleName().toString(), moduleMethod); 102 } 103 allMethodsByName.put(moduleMethod.getSimpleName().toString(), moduleMethod); 104 } 105 106 validateModuleVisibility(subject, builder); 107 validateMethodsWithSameName(builder, bindingMethodsByName); 108 if (subject.getKind() != ElementKind.INTERFACE) { 109 validateProvidesOverrides(subject, builder, allMethodsByName, bindingMethodsByName); 110 } 111 validateModifiers(subject, builder); 112 validateReferencedModules(subject, builder); 113 114 // TODO(gak): port the dagger 1 module validation? 115 return builder.build(); 116 } 117 validateModifiers( TypeElement subject, ValidationReport.Builder<TypeElement> builder)118 private void validateModifiers( 119 TypeElement subject, ValidationReport.Builder<TypeElement> builder) { 120 // This coupled with the check for abstract modules in ComponentValidator guarantees that 121 // only modules without type parameters are referenced from @Component(modules={...}). 122 if (!subject.getTypeParameters().isEmpty() && !subject.getModifiers().contains(ABSTRACT)) { 123 builder.addError(MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT, subject); 124 } 125 } 126 validateMethodsWithSameName( ValidationReport.Builder<TypeElement> builder, ListMultimap<String, ExecutableElement> bindingMethodsByName)127 private void validateMethodsWithSameName( 128 ValidationReport.Builder<TypeElement> builder, 129 ListMultimap<String, ExecutableElement> bindingMethodsByName) { 130 for (Entry<String, Collection<ExecutableElement>> entry : 131 bindingMethodsByName.asMap().entrySet()) { 132 if (entry.getValue().size() > 1) { 133 for (ExecutableElement offendingMethod : entry.getValue()) { 134 builder.addError( 135 String.format(BINDING_METHOD_WITH_SAME_NAME, methodClass.getSimpleName()), 136 offendingMethod); 137 } 138 } 139 } 140 } 141 validateReferencedModules( TypeElement subject, ValidationReport.Builder<TypeElement> builder)142 private void validateReferencedModules( 143 TypeElement subject, ValidationReport.Builder<TypeElement> builder) { 144 // Validate that all the modules we include are valid for inclusion. 145 AnnotationMirror mirror = getAnnotationMirror(subject, moduleClass).get(); 146 ImmutableList<TypeMirror> includedTypes = getModuleIncludes(mirror); 147 validateReferencedModules(subject, builder, includedTypes); 148 } 149 150 /** 151 * Used by {@link ModuleValidator} & {@link ComponentValidator} to validate referenced modules. 152 */ validateReferencedModules( final TypeElement subject, final ValidationReport.Builder<TypeElement> builder, ImmutableList<TypeMirror> includedTypes)153 void validateReferencedModules( 154 final TypeElement subject, 155 final ValidationReport.Builder<TypeElement> builder, 156 ImmutableList<TypeMirror> includedTypes) { 157 for (TypeMirror includedType : includedTypes) { 158 includedType.accept( 159 new SimpleTypeVisitor6<Void, Void>() { 160 @Override 161 protected Void defaultAction(TypeMirror mirror, Void p) { 162 builder.addError(mirror + " is not a valid module type.", subject); 163 return null; 164 } 165 166 @Override 167 public Void visitDeclared(DeclaredType t, Void p) { 168 final TypeElement element = MoreElements.asType(t.asElement()); 169 if (!t.getTypeArguments().isEmpty()) { 170 builder.addError( 171 String.format( 172 REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS, element.getQualifiedName()), 173 subject); 174 } 175 boolean isIncludedModule = 176 any( 177 includedModuleClasses, 178 new Predicate<Class<? extends Annotation>>() { 179 @Override 180 public boolean apply(Class<? extends Annotation> otherClass) { 181 return MoreElements.isAnnotationPresent(element, otherClass); 182 } 183 }); 184 if (!isIncludedModule) { 185 builder.addError( 186 String.format( 187 REFERENCED_MODULE_NOT_ANNOTATED, 188 element.getQualifiedName(), 189 (includedModuleClasses.size() > 1 ? "one of " : "") 190 + Joiner.on(", ") 191 .join( 192 FluentIterable.from(includedModuleClasses) 193 .transform( 194 new Function<Class<? extends Annotation>, String>() { 195 @Override 196 public String apply( 197 Class<? extends Annotation> otherClass) { 198 return "@" + otherClass.getSimpleName(); 199 } 200 }))), 201 subject); 202 } 203 if (element.getModifiers().contains(ABSTRACT)) { 204 builder.addError( 205 String.format( 206 REFERENCED_MODULES_MUST_NOT_BE_ABSTRACT, element.getQualifiedName()), 207 subject); 208 } 209 return null; 210 } 211 }, 212 null); 213 } 214 } 215 validateProvidesOverrides( TypeElement subject, ValidationReport.Builder<TypeElement> builder, ListMultimap<String, ExecutableElement> allMethodsByName, ListMultimap<String, ExecutableElement> bindingMethodsByName)216 private void validateProvidesOverrides( 217 TypeElement subject, 218 ValidationReport.Builder<TypeElement> builder, 219 ListMultimap<String, ExecutableElement> allMethodsByName, 220 ListMultimap<String, ExecutableElement> bindingMethodsByName) { 221 // For every @Provides method, confirm it overrides nothing *and* nothing overrides it. 222 // Consider the following hierarchy: 223 // class Parent { 224 // @Provides Foo a() {} 225 // @Provides Foo b() {} 226 // Foo c() {} 227 // } 228 // class Child extends Parent { 229 // @Provides Foo a() {} 230 // Foo b() {} 231 // @Provides Foo c() {} 232 // } 233 // In each of those cases, we want to fail. "a" is clear, "b" because Child is overriding 234 // a method marked @Provides in Parent, and "c" because Child is defining an @Provides 235 // method that overrides Parent. 236 TypeElement currentClass = subject; 237 TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType(); 238 // We keep track of methods that failed so we don't spam with multiple failures. 239 Set<ExecutableElement> failedMethods = Sets.newHashSet(); 240 while (!types.isSameType(currentClass.getSuperclass(), objectType)) { 241 currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass())); 242 List<ExecutableElement> superclassMethods = 243 ElementFilter.methodsIn(currentClass.getEnclosedElements()); 244 for (ExecutableElement superclassMethod : superclassMethods) { 245 String name = superclassMethod.getSimpleName().toString(); 246 // For each method in the superclass, confirm our @Provides methods don't override it 247 for (ExecutableElement providesMethod : bindingMethodsByName.get(name)) { 248 if (!failedMethods.contains(providesMethod) 249 && elements.overrides(providesMethod, superclassMethod, subject)) { 250 failedMethods.add(providesMethod); 251 builder.addError( 252 String.format( 253 PROVIDES_METHOD_OVERRIDES_ANOTHER, 254 methodClass.getSimpleName(), 255 methodSignatureFormatter.format(superclassMethod)), 256 providesMethod); 257 } 258 } 259 // For each @Provides method in superclass, confirm our methods don't override it. 260 if (isAnnotationPresent(superclassMethod, methodClass)) { 261 for (ExecutableElement method : allMethodsByName.get(name)) { 262 if (!failedMethods.contains(method) 263 && elements.overrides(method, superclassMethod, subject)) { 264 failedMethods.add(method); 265 builder.addError( 266 String.format( 267 METHOD_OVERRIDES_PROVIDES_METHOD, 268 methodClass.getSimpleName(), 269 methodSignatureFormatter.format(superclassMethod)), 270 method); 271 } 272 } 273 } 274 allMethodsByName.put(superclassMethod.getSimpleName().toString(), superclassMethod); 275 } 276 } 277 } 278 validateModuleVisibility(final TypeElement moduleElement, final ValidationReport.Builder<?> reportBuilder)279 private void validateModuleVisibility(final TypeElement moduleElement, 280 final ValidationReport.Builder<?> reportBuilder) { 281 Visibility moduleVisibility = Visibility.ofElement(moduleElement); 282 if (moduleVisibility.equals(PRIVATE)) { 283 reportBuilder.addError("Modules cannot be private.", moduleElement); 284 } else if (effectiveVisibilityOfElement(moduleElement).equals(PRIVATE)) { 285 reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement); 286 } 287 288 switch (moduleElement.getNestingKind()) { 289 case ANONYMOUS: 290 throw new IllegalStateException("Can't apply @Module to an anonymous class"); 291 case LOCAL: 292 throw new IllegalStateException("Local classes shouldn't show up in the processor"); 293 case MEMBER: 294 case TOP_LEVEL: 295 if (moduleVisibility.equals(PUBLIC)) { 296 ImmutableSet<Element> nonPublicModules = FluentIterable.from(getModuleIncludes( 297 getAnnotationMirror(moduleElement, moduleClass).get())) 298 .transform(new Function<TypeMirror, Element>() { 299 @Override public Element apply(TypeMirror input) { 300 return types.asElement(input); 301 } 302 }) 303 .filter(new Predicate<Element>() { 304 @Override public boolean apply(Element input) { 305 return effectiveVisibilityOfElement(input).compareTo(PUBLIC) < 0; 306 } 307 }) 308 .toSet(); 309 if (!nonPublicModules.isEmpty()) { 310 reportBuilder.addError( 311 String.format( 312 "This module is public, but it includes non-public " 313 + "(or effectively non-public) modules. " 314 + "Either reduce the visibility of this module or make %s public.", 315 formatListForErrorMessage(nonPublicModules.asList())), 316 moduleElement); 317 } 318 } 319 break; 320 default: 321 throw new AssertionError(); 322 } 323 } 324 formatListForErrorMessage(List<?> things)325 private static String formatListForErrorMessage(List<?> things) { 326 switch (things.size()) { 327 case 0: 328 return ""; 329 case 1: 330 return things.get(0).toString(); 331 default: 332 StringBuilder output = new StringBuilder(); 333 Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1)); 334 output.append(" and ").append(things.get(things.size() - 1)); 335 return output.toString(); 336 } 337 } 338 } 339