• 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.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