• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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