1 /* 2 * Copyright (C) 2018 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.common.base.Preconditions.checkState; 20 import static com.google.common.base.Throwables.getStackTraceAsString; 21 import static com.google.common.collect.Sets.difference; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 24 import static javax.tools.Diagnostic.Kind.ERROR; 25 26 import androidx.room.compiler.processing.XElement; 27 import androidx.room.compiler.processing.XMessager; 28 import androidx.room.compiler.processing.XProcessingEnv; 29 import androidx.room.compiler.processing.XProcessingStep; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.collect.ImmutableSetMultimap; 33 import com.google.common.collect.Maps; 34 import com.squareup.javapoet.ClassName; 35 import dagger.internal.codegen.base.DaggerSuperficialValidation.ValidationException; 36 import dagger.internal.codegen.compileroption.CompilerOptions; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Set; 41 import javax.inject.Inject; 42 43 /** 44 * A {@link XProcessingStep} that processes one element at a time and defers any for which {@link 45 * TypeNotPresentException} is thrown. 46 */ 47 public abstract class TypeCheckingProcessingStep<E extends XElement> implements XProcessingStep { 48 49 private final List<String> lastDeferredErrorMessages = new ArrayList<>(); 50 @Inject XMessager messager; 51 @Inject CompilerOptions compilerOptions; 52 @Inject SuperficialValidator superficialValidator; 53 54 @Override annotations()55 public final ImmutableSet<String> annotations() { 56 return annotationClassNames().stream().map(ClassName::canonicalName).collect(toImmutableSet()); 57 } 58 59 @SuppressWarnings("unchecked") // Subclass must ensure all annotated targets are of valid type. 60 @Override process( XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation)61 public ImmutableSet<XElement> process( 62 XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { 63 // We only really care about the deferred error messages from the final round of processing. 64 // Thus, we can clear the values stored from the previous processing round since that clearly 65 // wasn't the final round, and we replace it with any deferred error messages from this round. 66 lastDeferredErrorMessages.clear(); 67 ImmutableSet.Builder<XElement> deferredElements = ImmutableSet.builder(); 68 inverse(elementsByAnnotation) 69 .forEach( 70 (element, annotations) -> { 71 try { 72 // The XBasicAnnotationProcessor only validates the element itself. However, we 73 // validate the enclosing type here to keep the previous behavior of 74 // BasicAnnotationProcessor, since Dagger still relies on this behavior. 75 // TODO(b/201479062): It's inefficient to require validation of the entire enclosing 76 // type, we should try to remove this and handle any additional validation into the 77 // steps that need it. 78 superficialValidator.throwIfNearestEnclosingTypeNotValid(element); 79 process((E) element, annotations); 80 } catch (TypeNotPresentException e) { 81 // TODO(bcorso): We should be able to remove this once we replace all calls to 82 // SuperficialValidation with DaggerSuperficialValidation. 83 deferredElements.add(element); 84 cacheErrorMessage(typeNotPresentErrorMessage(element, e), e); 85 } catch (ValidationException.UnexpectedException unexpectedException) { 86 // Rethrow since the exception was created from an unexpected throwable so 87 // deferring to another round is unlikely to help. 88 throw unexpectedException; 89 } catch (ValidationException.KnownErrorType e) { 90 deferredElements.add(element); 91 cacheErrorMessage(knownErrorTypeErrorMessage(element, e), e); 92 } catch (ValidationException.UnknownErrorType e) { 93 deferredElements.add(element); 94 cacheErrorMessage(unknownErrorTypeErrorMessage(element, e), e); 95 } 96 }); 97 return deferredElements.build(); 98 } 99 100 @Override processOver( XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation)101 public void processOver( 102 XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { 103 // We avoid doing any actual processing here since this is run in the same round as the last 104 // call to process(). Instead, we just report the last deferred error messages, if any. 105 lastDeferredErrorMessages.forEach(errorMessage -> messager.printMessage(ERROR, errorMessage)); 106 lastDeferredErrorMessages.clear(); 107 } 108 cacheErrorMessage(String errorMessage, Exception exception)109 private void cacheErrorMessage(String errorMessage, Exception exception) { 110 lastDeferredErrorMessages.add( 111 compilerOptions.includeStacktraceWithDeferredErrorMessages() 112 ? String.format("%s\n\n%s", errorMessage, getStackTraceAsString(exception)) 113 : errorMessage); 114 } 115 typeNotPresentErrorMessage(XElement element, TypeNotPresentException exception)116 private String typeNotPresentErrorMessage(XElement element, TypeNotPresentException exception) { 117 return String.format( 118 "%1$s was unable to process '%2$s' because '%3$s' could not be resolved." 119 + "\n" 120 + "\nIf type '%3$s' is a generated type, check above for compilation errors that may " 121 + "have prevented the type from being generated. Otherwise, ensure that type '%3$s' is " 122 + "on your classpath.", 123 this.getClass().getSimpleName(), 124 element, 125 exception.typeName()); 126 } 127 knownErrorTypeErrorMessage( XElement element, ValidationException.KnownErrorType exception)128 private String knownErrorTypeErrorMessage( 129 XElement element, ValidationException.KnownErrorType exception) { 130 return String.format( 131 "%1$s was unable to process '%2$s' because '%3$s' could not be resolved." 132 + "\n" 133 + "\nDependency trace:" 134 + "\n => %4$s" 135 + "\n" 136 + "\nIf type '%3$s' is a generated type, check above for compilation errors that may " 137 + "have prevented the type from being generated. Otherwise, ensure that type '%3$s' is " 138 + "on your classpath.", 139 this.getClass().getSimpleName(), 140 element, 141 exception.getErrorTypeName(), 142 exception.getTrace()); 143 } 144 unknownErrorTypeErrorMessage( XElement element, ValidationException.UnknownErrorType exception)145 private String unknownErrorTypeErrorMessage( 146 XElement element, ValidationException.UnknownErrorType exception) { 147 return String.format( 148 "%1$s was unable to process '%2$s' because one of its dependencies could not be resolved." 149 + "\n" 150 + "\nDependency trace:" 151 + "\n => %3$s" 152 + "\n" 153 + "\nIf the dependency is a generated type, check above for compilation errors that may" 154 + " have prevented the type from being generated. Otherwise, ensure that the dependency" 155 + " is on your classpath.", 156 this.getClass().getSimpleName(), element, exception.getTrace()); 157 } 158 159 /** 160 * Processes one element. If this method throws {@link TypeNotPresentException}, the element will 161 * be deferred until the next round of processing. 162 * 163 * @param annotations the subset of {@link XProcessingStep#annotations()} that annotate {@code 164 * element} 165 */ process(E element, ImmutableSet<ClassName> annotations)166 protected abstract void process(E element, ImmutableSet<ClassName> annotations); 167 inverse( Map<String, ? extends Set<? extends XElement>> elementsByAnnotation)168 private ImmutableMap<XElement, ImmutableSet<ClassName>> inverse( 169 Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { 170 ImmutableMap<String, ClassName> annotationClassNames = 171 annotationClassNames().stream() 172 .collect(toImmutableMap(ClassName::canonicalName, className -> className)); 173 checkState( 174 annotationClassNames.keySet().containsAll(elementsByAnnotation.keySet()), 175 "Unexpected annotations for %s: %s", 176 this.getClass().getCanonicalName(), 177 difference(elementsByAnnotation.keySet(), annotationClassNames.keySet())); 178 179 ImmutableSetMultimap.Builder<XElement, ClassName> builder = ImmutableSetMultimap.builder(); 180 elementsByAnnotation.forEach( 181 (annotationName, elementSet) -> 182 elementSet.forEach( 183 element -> builder.put(element, annotationClassNames.get(annotationName)))); 184 185 return ImmutableMap.copyOf(Maps.transformValues(builder.build().asMap(), ImmutableSet::copyOf)); 186 } 187 188 /** Returns the set of annotations processed by this processing step. */ annotationClassNames()189 protected abstract Set<ClassName> annotationClassNames(); 190 } 191