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.squareup.javapoet.CodeBlock; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.Optional; 35 import javax.lang.model.element.AnnotationMirror; 36 import javax.lang.model.element.AnnotationValue; 37 import javax.lang.model.element.ExecutableElement; 38 import javax.lang.model.element.TypeElement; 39 import javax.lang.model.type.TypeMirror; 40 import javax.lang.model.util.SimpleAnnotationValueVisitor8; 41 42 /** 43 * A {@linkplain TypeMirror type} and an optional {@linkplain javax.inject.Qualifier qualifier} that 44 * is the lookup key for a binding. 45 */ 46 @AutoValue 47 public abstract class Key { 48 /** 49 * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix 50 * for the type of this key. 51 */ qualifier()52 public final Optional<AnnotationMirror> qualifier() { 53 return wrappedQualifier().map(Wrapper::get); 54 } 55 56 /** 57 * The type represented by this key. 58 */ type()59 public final TypeMirror type() { 60 return wrappedType().get(); 61 } 62 63 /** 64 * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix 65 * for the type of this key. 66 * 67 * Despite documentation in {@link AnnotationMirror}, equals and hashCode aren't implemented 68 * to represent logical equality, so {@link AnnotationMirrors#equivalence()} 69 * provides this facility. 70 */ wrappedQualifier()71 abstract Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedQualifier(); 72 73 /** 74 * The type represented by this key. 75 * 76 * As documented in {@link TypeMirror}, equals and hashCode aren't implemented to represent 77 * logical equality, so {@link MoreTypes#equivalence()} wraps this type. 78 */ wrappedType()79 abstract Equivalence.Wrapper<TypeMirror> wrappedType(); 80 81 /** 82 * Distinguishes keys for multibinding contributions that share a {@link #type()} and {@link 83 * #qualifier()}. 84 * 85 * <p>Each multibound map and set has a synthetic multibinding that depends on the specific 86 * contributions to that map or set using keys that identify those multibinding contributions. 87 * 88 * <p>Absent except for multibinding contributions. 89 */ multibindingContributionIdentifier()90 public abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier(); 91 92 /** Returns a {@link Builder} that inherits the properties of this key. */ toBuilder()93 public abstract Builder toBuilder(); 94 95 // The main hashCode/equality bottleneck is in MoreTypes.equivalence(). It's possible that we can 96 // avoid this by tuning that method. Perhaps we can also avoid the issue entirely by interning all 97 // Keys 98 @Memoized 99 @Override hashCode()100 public abstract int hashCode(); 101 102 @Override equals(Object o)103 public abstract boolean equals(Object o); 104 105 /** 106 * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order 107 * defined in the annotation type. This will produce the same output for {@linkplain 108 * AnnotationMirrors#equivalence() equal} {@link AnnotationMirror}s even if default values are 109 * omitted or their attributes were written in different orders, e.g. {@code @A(b = "b", c = "c")} 110 * and {@code @A(c = "c", b = "b", attributeWithDefaultValue = "default value")}. 111 */ 112 // TODO(ronshapiro): move this to auto-common stableAnnotationMirrorToString(AnnotationMirror qualifier)113 static String stableAnnotationMirrorToString(AnnotationMirror qualifier) { 114 StringBuilder builder = new StringBuilder("@").append(qualifier.getAnnotationType()); 115 ImmutableMap<ExecutableElement, AnnotationValue> elementValues = 116 AnnotationMirrors.getAnnotationValuesWithDefaults(qualifier); 117 if (!elementValues.isEmpty()) { 118 ImmutableMap.Builder<String, String> namedValuesBuilder = ImmutableMap.builder(); 119 elementValues.forEach( 120 (key, value) -> 121 namedValuesBuilder.put( 122 key.getSimpleName().toString(), stableAnnotationValueToString(value))); 123 ImmutableMap<String, String> namedValues = namedValuesBuilder.build(); 124 builder.append('('); 125 if (namedValues.size() == 1 && namedValues.containsKey("value")) { 126 // Omit "value =" 127 builder.append(namedValues.get("value")); 128 } else { 129 builder.append(Joiner.on(", ").withKeyValueSeparator("=").join(namedValues)); 130 } 131 builder.append(')'); 132 } 133 return builder.toString(); 134 } 135 stableAnnotationValueToString(AnnotationValue annotationValue)136 private static String stableAnnotationValueToString(AnnotationValue annotationValue) { 137 return annotationValue.accept( 138 new SimpleAnnotationValueVisitor8<String, Void>() { 139 @Override 140 protected String defaultAction(Object value, Void ignore) { 141 return value.toString(); 142 } 143 144 @Override 145 public String visitString(String value, Void ignore) { 146 return CodeBlock.of("$S", value).toString(); 147 } 148 149 @Override 150 public String visitAnnotation(AnnotationMirror value, Void ignore) { 151 return stableAnnotationMirrorToString(value); 152 } 153 154 @Override 155 public String visitArray(List<? extends AnnotationValue> value, Void ignore) { 156 return value.stream() 157 .map(Key::stableAnnotationValueToString) 158 .collect(joining(", ", "{", "}")); 159 } 160 }, 161 null); 162 } 163 164 @Override 165 public final String toString() { 166 return Joiner.on(' ') 167 .skipNulls() 168 .join( 169 qualifier().map(Key::stableAnnotationMirrorToString).orElse(null), 170 type(), 171 multibindingContributionIdentifier().orElse(null)); 172 } 173 174 /** Returns a builder for {@link Key}s. */ 175 public static Builder builder(TypeMirror type) { 176 return new AutoValue_Key.Builder().type(type); 177 } 178 179 /** A builder for {@link Key}s. */ 180 @AutoValue.Builder 181 public abstract static class Builder { 182 abstract Builder wrappedType(Equivalence.Wrapper<TypeMirror> wrappedType); 183 184 @CanIgnoreReturnValue 185 public final Builder type(TypeMirror type) { 186 return wrappedType(MoreTypes.equivalence().wrap(checkNotNull(type))); 187 } 188 189 abstract Builder wrappedQualifier( 190 Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedQualifier); 191 192 abstract Builder wrappedQualifier(Equivalence.Wrapper<AnnotationMirror> wrappedQualifier); 193 194 @CanIgnoreReturnValue 195 public final Builder qualifier(AnnotationMirror qualifier) { 196 return wrappedQualifier(AnnotationMirrors.equivalence().wrap(checkNotNull(qualifier))); 197 } 198 199 @CanIgnoreReturnValue 200 public final Builder qualifier(Optional<AnnotationMirror> qualifier) { 201 return wrappedQualifier(checkNotNull(qualifier).map(AnnotationMirrors.equivalence()::wrap)); 202 } 203 204 public abstract Builder multibindingContributionIdentifier( 205 Optional<MultibindingContributionIdentifier> identifier); 206 207 public abstract Builder multibindingContributionIdentifier( 208 MultibindingContributionIdentifier identifier); 209 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