• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.model;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static java.util.stream.Collectors.joining;
21 
22 import com.google.auto.common.AnnotationMirrors;
23 import com.google.auto.common.MoreTypes;
24 import com.google.auto.value.AutoValue;
25 import com.google.auto.value.extension.memoized.Memoized;
26 import com.google.common.base.Equivalence;
27 import com.google.common.base.Equivalence.Wrapper;
28 import com.google.common.base.Joiner;
29 import com.google.common.collect.ImmutableMap;
30 import com.google.errorprone.annotations.CanIgnoreReturnValue;
31 import com.google.errorprone.annotations.CheckReturnValue;
32 import com.squareup.javapoet.CodeBlock;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.Optional;
36 import javax.lang.model.element.AnnotationMirror;
37 import javax.lang.model.element.AnnotationValue;
38 import javax.lang.model.element.ExecutableElement;
39 import javax.lang.model.element.TypeElement;
40 import javax.lang.model.type.TypeMirror;
41 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
42 
43 /**
44  * A {@linkplain TypeMirror type} and an optional {@linkplain javax.inject.Qualifier qualifier} that
45  * is the lookup key for a binding.
46  */
47 @AutoValue
48 public abstract class Key {
49   /**
50    * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix
51    * for the type of this key.
52    */
qualifier()53   public final Optional<AnnotationMirror> qualifier() {
54     return wrappedQualifier().map(Wrapper::get);
55   }
56 
57   /**
58    * The type represented by this key.
59    */
type()60   public final TypeMirror type() {
61     return wrappedType().get();
62   }
63 
64   /**
65    * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix
66    * for the type of this key.
67    *
68    * Despite documentation in {@link AnnotationMirror}, equals and hashCode aren't implemented
69    * to represent logical equality, so {@link AnnotationMirrors#equivalence()}
70    * provides this facility.
71    */
wrappedQualifier()72   abstract Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedQualifier();
73 
74   /**
75    * The type represented by this key.
76    *
77    * As documented in {@link TypeMirror}, equals and hashCode aren't implemented to represent
78    * logical equality, so {@link MoreTypes#equivalence()} wraps this type.
79    */
wrappedType()80   abstract Equivalence.Wrapper<TypeMirror> wrappedType();
81 
82   /**
83    * Distinguishes keys for multibinding contributions that share a {@link #type()} and {@link
84    * #qualifier()}.
85    *
86    * <p>Each multibound map and set has a synthetic multibinding that depends on the specific
87    * contributions to that map or set using keys that identify those multibinding contributions.
88    *
89    * <p>Absent except for multibinding contributions.
90    */
multibindingContributionIdentifier()91   public abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier();
92 
93   /** Returns a {@link Builder} that inherits the properties of this key. */
toBuilder()94   public abstract Builder toBuilder();
95 
96   // The main hashCode/equality bottleneck is in MoreTypes.equivalence(). It's possible that we can
97   // avoid this by tuning that method. Perhaps we can also avoid the issue entirely by interning all
98   // Keys
99   @Memoized
100   @Override
hashCode()101   public abstract int hashCode();
102 
103   @Override
equals(Object o)104   public abstract boolean equals(Object o);
105 
106   /**
107    * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order
108    * defined in the annotation type. This will produce the same output for {@linkplain
109    * AnnotationMirrors#equivalence() equal} {@link AnnotationMirror}s even if default values are
110    * omitted or their attributes were written in different orders, e.g. {@code @A(b = "b", c = "c")}
111    * and {@code @A(c = "c", b = "b", attributeWithDefaultValue = "default value")}.
112    */
113   // TODO(ronshapiro): move this to auto-common
stableAnnotationMirrorToString(AnnotationMirror qualifier)114   private static String stableAnnotationMirrorToString(AnnotationMirror qualifier) {
115     StringBuilder builder = new StringBuilder("@").append(qualifier.getAnnotationType());
116     ImmutableMap<ExecutableElement, AnnotationValue> elementValues =
117         AnnotationMirrors.getAnnotationValuesWithDefaults(qualifier);
118     if (!elementValues.isEmpty()) {
119       ImmutableMap.Builder<String, String> namedValuesBuilder = ImmutableMap.builder();
120       elementValues.forEach(
121           (key, value) ->
122               namedValuesBuilder.put(
123                   key.getSimpleName().toString(), stableAnnotationValueToString(value)));
124       ImmutableMap<String, String> namedValues = namedValuesBuilder.build();
125       builder.append('(');
126       if (namedValues.size() == 1 && namedValues.containsKey("value")) {
127         // Omit "value ="
128         builder.append(namedValues.get("value"));
129       } else {
130         builder.append(Joiner.on(", ").withKeyValueSeparator("=").join(namedValues));
131       }
132       builder.append(')');
133     }
134     return builder.toString();
135   }
136 
stableAnnotationValueToString(AnnotationValue annotationValue)137   private static String stableAnnotationValueToString(AnnotationValue annotationValue) {
138     return annotationValue.accept(
139         new SimpleAnnotationValueVisitor8<String, Void>() {
140           @Override
141           protected String defaultAction(Object value, Void ignore) {
142             return value.toString();
143           }
144 
145           @Override
146           public String visitString(String value, Void ignore) {
147             return CodeBlock.of("$S", value).toString();
148           }
149 
150           @Override
151           public String visitAnnotation(AnnotationMirror value, Void ignore) {
152             return stableAnnotationMirrorToString(value);
153           }
154 
155           @Override
156           public String visitArray(List<? extends AnnotationValue> value, Void ignore) {
157             return value.stream()
158                 .map(Key::stableAnnotationValueToString)
159                 .collect(joining(", ", "{", "}"));
160           }
161         },
162         null);
163   }
164 
165   @Override
166   public final String toString() {
167     return Joiner.on(' ')
168         .skipNulls()
169         .join(
170             qualifier().map(Key::stableAnnotationMirrorToString).orElse(null),
171             type(),
172             multibindingContributionIdentifier().orElse(null));
173   }
174 
175   /** Returns a builder for {@link Key}s. */
176   public static Builder builder(TypeMirror type) {
177     return new AutoValue_Key.Builder().type(type);
178   }
179 
180   /** A builder for {@link Key}s. */
181   @CanIgnoreReturnValue
182   @AutoValue.Builder
183   public abstract static class Builder {
184     abstract Builder wrappedType(Equivalence.Wrapper<TypeMirror> wrappedType);
185 
186     public final Builder type(TypeMirror type) {
187       return wrappedType(MoreTypes.equivalence().wrap(checkNotNull(type)));
188     }
189 
190     abstract Builder wrappedQualifier(
191         Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedQualifier);
192 
193     abstract Builder wrappedQualifier(Equivalence.Wrapper<AnnotationMirror> wrappedQualifier);
194 
195     public final Builder qualifier(AnnotationMirror qualifier) {
196       return wrappedQualifier(AnnotationMirrors.equivalence().wrap(checkNotNull(qualifier)));
197     }
198 
199     public final Builder qualifier(Optional<AnnotationMirror> qualifier) {
200       return wrappedQualifier(checkNotNull(qualifier).map(AnnotationMirrors.equivalence()::wrap));
201     }
202 
203     public abstract Builder multibindingContributionIdentifier(
204         Optional<MultibindingContributionIdentifier> identifier);
205 
206     public abstract Builder multibindingContributionIdentifier(
207         MultibindingContributionIdentifier identifier);
208 
209     @CheckReturnValue
210     public abstract Key build();
211   }
212 
213   /**
214    * An object that identifies a multibinding contribution method and the module class that
215    * contributes it to the graph.
216    *
217    * @see #multibindingContributionIdentifier()
218    */
219   public static final class MultibindingContributionIdentifier {
220     private final String module;
221     private final String bindingElement;
222 
223     /**
224      * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}.
225      * It is not part of a specified API and may change at any point.
226      */
227     @Deprecated
228     public MultibindingContributionIdentifier(
229         // TODO(ronshapiro): reverse the order of these parameters
230         ExecutableElement bindingMethod, TypeElement contributingModule) {
231       this(
232           bindingMethod.getSimpleName().toString(),
233           contributingModule.getQualifiedName().toString());
234     }
235 
236     // TODO(ronshapiro,dpb): create KeyProxies so that these constructors don't need to be public.
237     @Deprecated
238     public MultibindingContributionIdentifier(String bindingElement, String module) {
239       this.module = module;
240       this.bindingElement = bindingElement;
241     }
242 
243     /**
244      * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}.
245      * It is not part of a specified API and may change at any point.
246      */
247     @Deprecated
248     public String module() {
249       return module;
250     }
251 
252     /**
253      * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}.
254      * It is not part of a specified API and may change at any point.
255      */
256     @Deprecated
257     public String bindingElement() {
258       return bindingElement;
259     }
260 
261     /**
262      * {@inheritDoc}
263      *
264      * <p>The returned string is human-readable and distinguishes the keys in the same way as the
265      * whole object.
266      */
267     @Override
268     public String toString() {
269       return String.format("%s#%s", module, bindingElement);
270     }
271 
272     @Override
273     public boolean equals(Object obj) {
274       if (obj instanceof MultibindingContributionIdentifier) {
275         MultibindingContributionIdentifier other = (MultibindingContributionIdentifier) obj;
276         return module.equals(other.module) && bindingElement.equals(other.bindingElement);
277       }
278       return false;
279     }
280 
281     @Override
282     public int hashCode() {
283       return Objects.hash(module, bindingElement);
284     }
285   }
286 }
287