| /* |
| * Copyright (C) 2021 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.internal.codegen.xprocessing; |
| |
| import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; |
| import static androidx.room.compiler.processing.compat.XConverters.toJavac; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; |
| import static java.util.stream.Collectors.joining; |
| |
| import androidx.room.compiler.processing.JavaPoetExtKt; |
| import androidx.room.compiler.processing.XAnnotation; |
| import androidx.room.compiler.processing.XProcessingEnv; |
| import androidx.room.compiler.processing.XType; |
| import androidx.room.compiler.processing.XTypeElement; |
| import com.google.auto.common.AnnotationMirrors; |
| import com.google.common.base.Equivalence; |
| import com.google.common.collect.ImmutableList; |
| import com.squareup.javapoet.AnnotationSpec; |
| import com.squareup.javapoet.ClassName; |
| import java.util.Arrays; |
| |
| // TODO(bcorso): Consider moving these methods into XProcessing library. |
| /** A utility class for {@link XAnnotation} helper methods. */ |
| public final class XAnnotations { |
| |
| /** Returns the {@link AnnotationSpec} for the given annotation */ |
| public static AnnotationSpec getAnnotationSpec(XAnnotation annotation) { |
| return JavaPoetExtKt.toAnnotationSpec(annotation, false); |
| } |
| |
| /** Returns the string representation of the given annotation. */ |
| public static String toString(XAnnotation annotation) { |
| // TODO(b/241293838): Make javac and ksp agree on the string representation. |
| return getProcessingEnv(annotation).getBackend() == XProcessingEnv.Backend.JAVAC |
| ? AnnotationMirrors.toString(toJavac(annotation)) |
| : XAnnotations.toStableString(annotation); |
| } |
| |
| /** Returns the class name of the given annotation */ |
| public static ClassName getClassName(XAnnotation annotation) { |
| return annotation.getType().getTypeElement().getClassName(); |
| } |
| |
| private static final Equivalence<XAnnotation> XANNOTATION_EQUIVALENCE = |
| new Equivalence<XAnnotation>() { |
| @Override |
| protected boolean doEquivalent(XAnnotation left, XAnnotation right) { |
| return XTypes.equivalence().equivalent(left.getType(), right.getType()) |
| && XAnnotationValues.equivalence() |
| .pairwise() |
| .equivalent(left.getAnnotationValues(), right.getAnnotationValues()); |
| } |
| |
| @Override |
| protected int doHash(XAnnotation annotation) { |
| return Arrays.hashCode( |
| new int[] { |
| XTypes.equivalence().hash(annotation.getType()), |
| XAnnotationValues.equivalence().pairwise().hash(annotation.getAnnotationValues()) |
| }); |
| } |
| |
| @Override |
| public String toString() { |
| return "XAnnotation.equivalence()"; |
| } |
| }; |
| |
| /** |
| * Returns an {@link Equivalence} for {@link XAnnotation}. |
| * |
| * <p>This equivalence takes into account the order of annotation values. |
| */ |
| public static Equivalence<XAnnotation> equivalence() { |
| return XANNOTATION_EQUIVALENCE; |
| } |
| |
| /** |
| * Returns a stable string representation of {@link XAnnotation}. |
| * |
| * <p>The output string will be the same regardless of whether default values were 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")} will both output the |
| * same string. This stability can be useful for things like serialization or reporting error |
| * messages. |
| */ |
| public static String toStableString(XAnnotation annotation) { |
| try { |
| // TODO(b/249283155): Due to a bug in XProcessing, calling various methods on an annotation |
| // that is an error type may throw an unexpected exception, so we just output the name. |
| if (annotation.getType().isError()) { |
| return "@" + annotation.getName(); // SUPPRESS_GET_NAME_CHECK |
| } |
| return annotation.getAnnotationValues().isEmpty() |
| // If the annotation doesn't have values then skip the empty parenthesis. |
| ? String.format("@%s", getClassName(annotation).canonicalName()) |
| : String.format( |
| "@%s(%s)", |
| getClassName(annotation).canonicalName(), |
| // The annotation values returned by XProcessing should already be in the order |
| // defined in the annotation class and include default values for any missing values. |
| annotation.getAnnotationValues().stream() |
| .map( |
| value -> { |
| String name = value.getName(); // SUPPRESS_GET_NAME_CHECK |
| String valueAsString = XAnnotationValues.toStableString(value); |
| // A single value with name "value" can output the value directly. |
| return annotation.getAnnotationValues().size() == 1 |
| && name.contentEquals("value") |
| ? valueAsString |
| : String.format("%s=%s", name, valueAsString); |
| }) |
| .collect(joining(", "))); |
| } catch (TypeNotPresentException e) { |
| return e.typeName(); |
| } |
| } |
| |
| /** Returns the value of the given [key] as a type element. */ |
| public static XTypeElement getAsTypeElement(XAnnotation annotation, String key) { |
| return annotation.getAsType(key).getTypeElement(); |
| } |
| |
| /** Returns the value of the given [key] as a list of type elements. */ |
| public static ImmutableList<XTypeElement> getAsTypeElementList( |
| XAnnotation annotation, String key) { |
| return annotation.getAsTypeList(key).stream() |
| .map(XType::getTypeElement) |
| .collect(toImmutableList()); |
| } |
| |
| private XAnnotations() {} |
| } |