1 /* 2 * Copyright (C) 2021 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.internal.codegen.xprocessing; 18 19 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; 20 import static androidx.room.compiler.processing.compat.XConverters.toJavac; 21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 22 import static java.util.stream.Collectors.joining; 23 24 import androidx.room.compiler.processing.JavaPoetExtKt; 25 import androidx.room.compiler.processing.XAnnotation; 26 import androidx.room.compiler.processing.XProcessingEnv; 27 import androidx.room.compiler.processing.XType; 28 import androidx.room.compiler.processing.XTypeElement; 29 import com.google.auto.common.AnnotationMirrors; 30 import com.google.common.base.Equivalence; 31 import com.google.common.collect.ImmutableList; 32 import com.squareup.javapoet.AnnotationSpec; 33 import com.squareup.javapoet.ClassName; 34 import java.util.Arrays; 35 36 // TODO(bcorso): Consider moving these methods into XProcessing library. 37 /** A utility class for {@link XAnnotation} helper methods. */ 38 public final class XAnnotations { 39 40 /** Returns the {@link AnnotationSpec} for the given annotation */ getAnnotationSpec(XAnnotation annotation)41 public static AnnotationSpec getAnnotationSpec(XAnnotation annotation) { 42 return JavaPoetExtKt.toAnnotationSpec(annotation, false); 43 } 44 45 /** Returns the string representation of the given annotation. */ toString(XAnnotation annotation)46 public static String toString(XAnnotation annotation) { 47 // TODO(b/241293838): Make javac and ksp agree on the string representation. 48 return getProcessingEnv(annotation).getBackend() == XProcessingEnv.Backend.JAVAC 49 ? AnnotationMirrors.toString(toJavac(annotation)) 50 : XAnnotations.toStableString(annotation); 51 } 52 53 /** Returns the class name of the given annotation */ getClassName(XAnnotation annotation)54 public static ClassName getClassName(XAnnotation annotation) { 55 return annotation.getType().getTypeElement().getClassName(); 56 } 57 58 private static final Equivalence<XAnnotation> XANNOTATION_EQUIVALENCE = 59 new Equivalence<XAnnotation>() { 60 @Override 61 protected boolean doEquivalent(XAnnotation left, XAnnotation right) { 62 return XTypes.equivalence().equivalent(left.getType(), right.getType()) 63 && XAnnotationValues.equivalence() 64 .pairwise() 65 .equivalent(left.getAnnotationValues(), right.getAnnotationValues()); 66 } 67 68 @Override 69 protected int doHash(XAnnotation annotation) { 70 return Arrays.hashCode( 71 new int[] { 72 XTypes.equivalence().hash(annotation.getType()), 73 XAnnotationValues.equivalence().pairwise().hash(annotation.getAnnotationValues()) 74 }); 75 } 76 77 @Override 78 public String toString() { 79 return "XAnnotation.equivalence()"; 80 } 81 }; 82 83 /** 84 * Returns an {@link Equivalence} for {@link XAnnotation}. 85 * 86 * <p>This equivalence takes into account the order of annotation values. 87 */ equivalence()88 public static Equivalence<XAnnotation> equivalence() { 89 return XANNOTATION_EQUIVALENCE; 90 } 91 92 /** 93 * Returns a stable string representation of {@link XAnnotation}. 94 * 95 * <p>The output string will be the same regardless of whether default values were omitted or 96 * their attributes were written in different orders, e.g. {@code @A(b = "b", c = "c")} and 97 * {@code @A(c = "c", b = "b", attributeWithDefaultValue = "default value")} will both output the 98 * same string. This stability can be useful for things like serialization or reporting error 99 * messages. 100 */ toStableString(XAnnotation annotation)101 public static String toStableString(XAnnotation annotation) { 102 try { 103 // TODO(b/249283155): Due to a bug in XProcessing, calling various methods on an annotation 104 // that is an error type may throw an unexpected exception, so we just output the name. 105 if (annotation.getType().isError()) { 106 return "@" + annotation.getName(); // SUPPRESS_GET_NAME_CHECK 107 } 108 return annotation.getAnnotationValues().isEmpty() 109 // If the annotation doesn't have values then skip the empty parenthesis. 110 ? String.format("@%s", getClassName(annotation).canonicalName()) 111 : String.format( 112 "@%s(%s)", 113 getClassName(annotation).canonicalName(), 114 // The annotation values returned by XProcessing should already be in the order 115 // defined in the annotation class and include default values for any missing values. 116 annotation.getAnnotationValues().stream() 117 .map( 118 value -> { 119 String name = value.getName(); // SUPPRESS_GET_NAME_CHECK 120 String valueAsString = XAnnotationValues.toStableString(value); 121 // A single value with name "value" can output the value directly. 122 return annotation.getAnnotationValues().size() == 1 123 && name.contentEquals("value") 124 ? valueAsString 125 : String.format("%s=%s", name, valueAsString); 126 }) 127 .collect(joining(", "))); 128 } catch (TypeNotPresentException e) { 129 return e.typeName(); 130 } 131 } 132 133 /** Returns the value of the given [key] as a type element. */ getAsTypeElement(XAnnotation annotation, String key)134 public static XTypeElement getAsTypeElement(XAnnotation annotation, String key) { 135 return annotation.getAsType(key).getTypeElement(); 136 } 137 138 /** Returns the value of the given [key] as a list of type elements. */ getAsTypeElementList( XAnnotation annotation, String key)139 public static ImmutableList<XTypeElement> getAsTypeElementList( 140 XAnnotation annotation, String key) { 141 return annotation.getAsTypeList(key).stream() 142 .map(XType::getTypeElement) 143 .collect(toImmutableList()); 144 } 145 XAnnotations()146 private XAnnotations() {} 147 } 148