/* * Copyright (C) 2014 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.model; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.stream.Collectors.joining; import com.google.auto.common.AnnotationMirrors; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import com.squareup.javapoet.CodeBlock; import java.util.List; import java.util.Objects; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor8; /** * A {@linkplain TypeMirror type} and an optional {@linkplain javax.inject.Qualifier qualifier} that * is the lookup key for a binding. */ @AutoValue public abstract class Key { /** * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix * for the type of this key. */ public final Optional qualifier() { return wrappedQualifier().map(Wrapper::get); } /** * The type represented by this key. */ public final TypeMirror type() { return wrappedType().get(); } /** * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix * for the type of this key. * * Despite documentation in {@link AnnotationMirror}, equals and hashCode aren't implemented * to represent logical equality, so {@link AnnotationMirrors#equivalence()} * provides this facility. */ abstract Optional> wrappedQualifier(); /** * The type represented by this key. * * As documented in {@link TypeMirror}, equals and hashCode aren't implemented to represent * logical equality, so {@link MoreTypes#equivalence()} wraps this type. */ abstract Equivalence.Wrapper wrappedType(); /** * Distinguishes keys for multibinding contributions that share a {@link #type()} and {@link * #qualifier()}. * *

Each multibound map and set has a synthetic multibinding that depends on the specific * contributions to that map or set using keys that identify those multibinding contributions. * *

Absent except for multibinding contributions. */ public abstract Optional multibindingContributionIdentifier(); /** Returns a {@link Builder} that inherits the properties of this key. */ public abstract Builder toBuilder(); // The main hashCode/equality bottleneck is in MoreTypes.equivalence(). It's possible that we can // avoid this by tuning that method. Perhaps we can also avoid the issue entirely by interning all // Keys @Memoized @Override public abstract int hashCode(); @Override public abstract boolean equals(Object o); /** * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order * defined in the annotation type. This will produce the same output for {@linkplain * AnnotationMirrors#equivalence() equal} {@link AnnotationMirror}s even if default values are * omitted or their attributes were written in different orders, e.g. {@code @A(b = "b", c = "c")} * and {@code @A(c = "c", b = "b", attributeWithDefaultValue = "default value")}. */ // TODO(ronshapiro): move this to auto-common private static String stableAnnotationMirrorToString(AnnotationMirror qualifier) { StringBuilder builder = new StringBuilder("@").append(qualifier.getAnnotationType()); ImmutableMap elementValues = AnnotationMirrors.getAnnotationValuesWithDefaults(qualifier); if (!elementValues.isEmpty()) { ImmutableMap.Builder namedValuesBuilder = ImmutableMap.builder(); elementValues.forEach( (key, value) -> namedValuesBuilder.put( key.getSimpleName().toString(), stableAnnotationValueToString(value))); ImmutableMap namedValues = namedValuesBuilder.build(); builder.append('('); if (namedValues.size() == 1 && namedValues.containsKey("value")) { // Omit "value =" builder.append(namedValues.get("value")); } else { builder.append(Joiner.on(", ").withKeyValueSeparator("=").join(namedValues)); } builder.append(')'); } return builder.toString(); } private static String stableAnnotationValueToString(AnnotationValue annotationValue) { return annotationValue.accept( new SimpleAnnotationValueVisitor8() { @Override protected String defaultAction(Object value, Void ignore) { return value.toString(); } @Override public String visitString(String value, Void ignore) { return CodeBlock.of("$S", value).toString(); } @Override public String visitAnnotation(AnnotationMirror value, Void ignore) { return stableAnnotationMirrorToString(value); } @Override public String visitArray(List value, Void ignore) { return value.stream() .map(Key::stableAnnotationValueToString) .collect(joining(", ", "{", "}")); } }, null); } @Override public final String toString() { return Joiner.on(' ') .skipNulls() .join( qualifier().map(Key::stableAnnotationMirrorToString).orElse(null), type(), multibindingContributionIdentifier().orElse(null)); } /** Returns a builder for {@link Key}s. */ public static Builder builder(TypeMirror type) { return new AutoValue_Key.Builder().type(type); } /** A builder for {@link Key}s. */ @CanIgnoreReturnValue @AutoValue.Builder public abstract static class Builder { abstract Builder wrappedType(Equivalence.Wrapper wrappedType); public final Builder type(TypeMirror type) { return wrappedType(MoreTypes.equivalence().wrap(checkNotNull(type))); } abstract Builder wrappedQualifier( Optional> wrappedQualifier); abstract Builder wrappedQualifier(Equivalence.Wrapper wrappedQualifier); public final Builder qualifier(AnnotationMirror qualifier) { return wrappedQualifier(AnnotationMirrors.equivalence().wrap(checkNotNull(qualifier))); } public final Builder qualifier(Optional qualifier) { return wrappedQualifier(checkNotNull(qualifier).map(AnnotationMirrors.equivalence()::wrap)); } public abstract Builder multibindingContributionIdentifier( Optional identifier); public abstract Builder multibindingContributionIdentifier( MultibindingContributionIdentifier identifier); @CheckReturnValue public abstract Key build(); } /** * An object that identifies a multibinding contribution method and the module class that * contributes it to the graph. * * @see #multibindingContributionIdentifier() */ public static final class MultibindingContributionIdentifier { private final String module; private final String bindingElement; /** * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}. * It is not part of a specified API and may change at any point. */ @Deprecated public MultibindingContributionIdentifier( // TODO(ronshapiro): reverse the order of these parameters ExecutableElement bindingMethod, TypeElement contributingModule) { this( bindingMethod.getSimpleName().toString(), contributingModule.getQualifiedName().toString()); } // TODO(ronshapiro,dpb): create KeyProxies so that these constructors don't need to be public. @Deprecated public MultibindingContributionIdentifier(String bindingElement, String module) { this.module = module; this.bindingElement = bindingElement; } /** * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}. * It is not part of a specified API and may change at any point. */ @Deprecated public String module() { return module; } /** * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}. * It is not part of a specified API and may change at any point. */ @Deprecated public String bindingElement() { return bindingElement; } /** * {@inheritDoc} * *

The returned string is human-readable and distinguishes the keys in the same way as the * whole object. */ @Override public String toString() { return String.format("%s#%s", module, bindingElement); } @Override public boolean equals(Object obj) { if (obj instanceof MultibindingContributionIdentifier) { MultibindingContributionIdentifier other = (MultibindingContributionIdentifier) obj; return module.equals(other.module) && bindingElement.equals(other.bindingElement); } return false; } @Override public int hashCode() { return Objects.hash(module, bindingElement); } } }