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