1/* 2 * Copyright (C) 2015 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 */ 16package dagger.internal.codegen; 17 18import com.google.auto.common.MoreTypes; 19import com.google.common.base.Equivalence; 20import com.google.common.collect.Iterables; 21import com.google.common.collect.LinkedHashMultimap; 22import com.google.common.collect.Multimap; 23import java.lang.annotation.Annotation; 24import java.util.Collection; 25import java.util.List; 26import java.util.Map; 27import java.util.Set; 28import javax.lang.model.element.Element; 29import javax.lang.model.element.ExecutableElement; 30import javax.lang.model.element.Modifier; 31import javax.lang.model.element.TypeElement; 32import javax.lang.model.type.ExecutableType; 33import javax.lang.model.type.TypeKind; 34import javax.lang.model.type.TypeMirror; 35import javax.lang.model.util.ElementFilter; 36import javax.lang.model.util.Elements; 37import javax.lang.model.util.Types; 38 39import static com.google.auto.common.MoreElements.isAnnotationPresent; 40import static com.google.common.base.Preconditions.checkArgument; 41import static com.google.common.collect.Iterables.getOnlyElement; 42import static javax.lang.model.element.Modifier.ABSTRACT; 43import static javax.lang.model.element.Modifier.PRIVATE; 44import static javax.lang.model.element.Modifier.STATIC; 45 46/** 47 * Validates {@link dagger.Component.Builder} annotations. 48 * 49 * @author sameb@google.com (Sam Berlin) 50 */ 51class BuilderValidator { 52 private final Elements elements; 53 private final Types types; 54 private final ComponentDescriptor.Kind componentType; 55 56 BuilderValidator(Elements elements, Types types, ComponentDescriptor.Kind componentType) { 57 this.elements = elements; 58 this.types = types; 59 this.componentType = componentType; 60 } 61 62 public ValidationReport<TypeElement> validate(TypeElement subject) { 63 ValidationReport.Builder<TypeElement> builder = ValidationReport.about(subject); 64 65 Element componentElement = subject.getEnclosingElement(); 66 ErrorMessages.ComponentBuilderMessages msgs = ErrorMessages.builderMsgsFor(componentType); 67 Class<? extends Annotation> componentAnnotation = componentType.annotationType(); 68 Class<? extends Annotation> builderAnnotation = componentType.builderAnnotationType(); 69 checkArgument(subject.getAnnotation(builderAnnotation) != null); 70 71 if (!isAnnotationPresent(componentElement, componentAnnotation)) { 72 builder.addError(msgs.mustBeInComponent(), subject); 73 } 74 75 switch (subject.getKind()) { 76 case CLASS: 77 List<? extends Element> allElements = subject.getEnclosedElements(); 78 List<ExecutableElement> cxtors = ElementFilter.constructorsIn(allElements); 79 if (cxtors.size() != 1 || getOnlyElement(cxtors).getParameters().size() != 0) { 80 builder.addError(msgs.cxtorOnlyOneAndNoArgs(), subject); 81 } 82 break; 83 case INTERFACE: 84 break; 85 default: 86 // If not the correct type, exit early since the rest of the messages will be bogus. 87 builder.addError(msgs.mustBeClassOrInterface(), subject); 88 return builder.build(); 89 } 90 91 if (!subject.getTypeParameters().isEmpty()) { 92 builder.addError(msgs.generics(), subject); 93 } 94 95 Set<Modifier> modifiers = subject.getModifiers(); 96 if (modifiers.contains(PRIVATE)) { 97 builder.addError(msgs.isPrivate(), subject); 98 } 99 if (!modifiers.contains(STATIC)) { 100 builder.addError(msgs.mustBeStatic(), subject); 101 } 102 // Note: Must be abstract, so no need to check for final. 103 if (!modifiers.contains(ABSTRACT)) { 104 builder.addError(msgs.mustBeAbstract(), subject); 105 } 106 107 ExecutableElement buildMethod = null; 108 Multimap<Equivalence.Wrapper<TypeMirror>, ExecutableElement> methodsPerParam = 109 LinkedHashMultimap.create(); 110 for (ExecutableElement method : Util.getUnimplementedMethods(elements, subject)) { 111 ExecutableType resolvedMethodType = 112 MoreTypes.asExecutable(types.asMemberOf(MoreTypes.asDeclared(subject.asType()), method)); 113 TypeMirror returnType = resolvedMethodType.getReturnType(); 114 if (method.getParameters().size() == 0) { 115 // If this is potentially a build() method, validate it returns the correct type. 116 if (types.isSameType(returnType, componentElement.asType())) { 117 if (buildMethod != null) { 118 // If we found more than one build-like method, fail. 119 error(builder, method, msgs.twoBuildMethods(), msgs.inheritedTwoBuildMethods(), 120 buildMethod); 121 } 122 } else { 123 error(builder, method, msgs.buildMustReturnComponentType(), 124 msgs.inheritedBuildMustReturnComponentType()); 125 } 126 // We set the buildMethod regardless of the return type to reduce error spam. 127 buildMethod = method; 128 } else if (method.getParameters().size() > 1) { 129 // If this is a setter, make sure it has one arg. 130 error(builder, method, msgs.methodsMustTakeOneArg(), msgs.inheritedMethodsMustTakeOneArg()); 131 } else if (returnType.getKind() != TypeKind.VOID 132 && !types.isSubtype(subject.asType(), returnType)) { 133 // If this correctly had one arg, make sure the return types are valid. 134 error(builder, method, msgs.methodsMustReturnVoidOrBuilder(), 135 msgs.inheritedMethodsMustReturnVoidOrBuilder()); 136 } else { 137 // If the return types are valid, record the method. 138 methodsPerParam.put( 139 MoreTypes.equivalence().<TypeMirror>wrap( 140 Iterables.getOnlyElement(resolvedMethodType.getParameterTypes())), 141 method); 142 } 143 144 if (!method.getTypeParameters().isEmpty()) { 145 error(builder, method, msgs.methodsMayNotHaveTypeParameters(), 146 msgs.inheritedMethodsMayNotHaveTypeParameters()); 147 } 148 } 149 150 if (buildMethod == null) { 151 builder.addError(msgs.missingBuildMethod(), subject); 152 } 153 154 // Go back through each recorded method per param type. If we had more than one method 155 // for a given param, fail. 156 for (Map.Entry<Equivalence.Wrapper<TypeMirror>, Collection<ExecutableElement>> entry : 157 methodsPerParam.asMap().entrySet()) { 158 if (entry.getValue().size() > 1) { 159 TypeMirror type = entry.getKey().get(); 160 builder.addError(String.format(msgs.manyMethodsForType(), type, entry.getValue()), subject); 161 } 162 } 163 164 // Note: there's more validation in BindingGraphValidator, 165 // specifically to make sure the setter methods mirror the deps. 166 167 return builder.build(); 168 } 169 170 /** 171 * Generates one of two error messages. If the method is enclosed in the subject, we target the 172 * error to the method itself. Otherwise we target the error to the subject and list the method as 173 * an argumnent. (Otherwise we have no way of knowing if the method is being compiled in this pass 174 * too, so javac might not be able to pinpoint it's line of code.) 175 */ 176 /* 177 * For Component.Builder, the prototypical example would be if someone had: 178 * libfoo: interface SharedBuilder { void badSetter(A a, B b); } 179 * libbar: BarComponent { BarBuilder extends SharedBuilder } } 180 * ... the compiler only validates BarBuilder when compiling libbar, but it fails because 181 * of libfoo's SharedBuilder (which could have been compiled in a previous pass). 182 * So we can't point to SharedBuilder#badSetter as the subject of the BarBuilder validation 183 * failure. 184 * 185 * This check is a little more strict than necessary -- ideally we'd check if method's enclosing 186 * class was included in this compile run. But that's hard, and this is close enough. 187 */ 188 private void error( 189 ValidationReport.Builder<TypeElement> builder, 190 ExecutableElement method, 191 String enclosedError, 192 String inheritedError, 193 Object... extraArgs) { 194 if (method.getEnclosingElement().equals(builder.getSubject())) { 195 builder.addError(String.format(enclosedError, extraArgs), method); 196 } else { 197 Object[] newArgs = new Object[extraArgs.length + 1]; 198 newArgs[0] = method; 199 System.arraycopy(extraArgs, 0, newArgs, 1, extraArgs.length); 200 builder.addError(String.format(inheritedError, newArgs), builder.getSubject()); 201 } 202 } 203} 204