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