• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 dagger.internal.codegen.xprocessing.XElements.getSimpleName;
20 import static java.lang.Character.isISOControl;
21 import static java.util.stream.Collectors.joining;
22 
23 import androidx.room.compiler.processing.XAnnotationValue;
24 import com.google.common.base.Equivalence;
25 import com.squareup.javapoet.CodeBlock;
26 
27 // TODO(bcorso): Consider moving these methods into XProcessing library.
28 /** A utility class for {@link XAnnotationValue} helper methods. */
29 public final class XAnnotationValues {
30   private static final Equivalence<XAnnotationValue> XANNOTATION_VALUE_EQUIVALENCE =
31       new Equivalence<XAnnotationValue>() {
32         @Override
33         protected boolean doEquivalent(XAnnotationValue left, XAnnotationValue right) {
34           if (left.hasAnnotationValue()) {
35             return right.hasAnnotationValue()
36                 && XAnnotations.equivalence().equivalent(left.asAnnotation(), right.asAnnotation());
37           } else if (left.hasListValue()) {
38             return right.hasListValue()
39                 && XAnnotationValues.equivalence()
40                     .pairwise()
41                     .equivalent(left.asAnnotationValueList(), right.asAnnotationValueList());
42           } else if (left.hasTypeValue()) {
43             return right.hasTypeValue()
44                 && XTypes.equivalence().equivalent(left.asType(), right.asType());
45           }
46           return left.getValue().equals(right.getValue());
47         }
48 
49         @Override
50         protected int doHash(XAnnotationValue value) {
51           if (value.hasAnnotationValue()) {
52             return XAnnotations.equivalence().hash(value.asAnnotation());
53           } else if (value.hasListValue()) {
54             return XAnnotationValues.equivalence().pairwise().hash(value.asAnnotationValueList());
55           } else if (value.hasTypeValue()) {
56             return XTypes.equivalence().hash(value.asType());
57           }
58           return value.getValue().hashCode();
59         }
60 
61         @Override
62         public String toString() {
63           return "XAnnotationValues.equivalence()";
64         }
65       };
66 
67   /** Returns an {@link Equivalence} for {@link XAnnotationValue}. */
equivalence()68   public static Equivalence<XAnnotationValue> equivalence() {
69     return XANNOTATION_VALUE_EQUIVALENCE;
70   }
71 
getKindName(XAnnotationValue value)72   public static String getKindName(XAnnotationValue value) {
73     if (value.hasAnnotationListValue()) {
74       return "ANNOTATION_ARRAY";
75     } else if (value.hasAnnotationValue()) {
76       return "ANNOTATION";
77     } else if (value.hasEnumListValue()) {
78       return "ENUM_ARRAY";
79     } else if (value.hasEnumValue()) {
80       return "ENUM";
81     } else if (value.hasTypeListValue()) {
82       return "TYPE_ARRAY";
83     } else if (value.hasTypeValue()) {
84       return "TYPE";
85     } else if (value.hasBooleanListValue()) {
86       return "BOOLEAN_ARRAY";
87     } else if (value.hasBooleanValue()) {
88       return "BOOLEAN";
89     } else if (value.hasByteListValue()) {
90       return "BYTE_ARRAY";
91     } else if (value.hasByteValue()) {
92       return "BYTE";
93     } else if (value.hasCharListValue()) {
94       return "CHAR_ARRAY";
95     } else if (value.hasCharValue()) {
96       return "CHAR";
97     } else if (value.hasDoubleListValue()) {
98       return "DOUBLE_ARRAY";
99     } else if (value.hasDoubleValue()) {
100       return "DOUBLE";
101     } else if (value.hasFloatListValue()) {
102       return "FLOAT_ARRAY";
103     } else if (value.hasFloatValue()) {
104       return "FLOAT";
105     } else if (value.hasIntListValue()) {
106       return "INT_ARRAY";
107     } else if (value.hasIntValue()) {
108       return "INT";
109     } else if (value.hasLongListValue()) {
110       return "LONG_ARRAY";
111     } else if (value.hasLongValue()) {
112       return "LONG";
113     } else if (value.hasShortListValue()) {
114       return "SHORT_ARRAY";
115     } else if (value.hasShortValue()) {
116       return "SHORT";
117     } else if (value.hasStringListValue()) {
118       return "STRING_ARRAY";
119     } else if (value.hasStringValue()) {
120       return "STRING";
121     } else {
122       return value.hasListValue() ? "UNKNOWN_ARRAY" : "UNKNOWN";
123     }
124   }
125 
toStableString(XAnnotationValue value)126   public static String toStableString(XAnnotationValue value) {
127     try {
128       // TODO(b/251786719): XProcessing handles error values differently in KSP and Javac. In Javac
129       // an exception is thrown for type "<error>", but in KSP the value is just null. We work
130       // around this here and try to give the same string regardless of the backend.
131       if (value.getValue() == null) {
132         return "<error>";
133       }
134       if (value.hasListValue()) {
135         // TODO(b/241834848): After this is fixed, consider skipping the braces for single values.
136         return value.asAnnotationValueList().stream()
137             .map(v -> toStableString(v))
138             .collect(joining(", ", "{", "}"));
139       } else if (value.hasAnnotationValue()) {
140         return XAnnotations.toStableString(value.asAnnotation());
141       } else if (value.hasEnumValue()) {
142         return getSimpleName(value.asEnum());
143       } else if (value.hasTypeValue()) {
144         return value.asType().getTypeElement().getQualifiedName();
145       } else if (value.hasStringValue()) {
146         return CodeBlock.of("$S", value.asString()).toString();
147       } else if (value.hasCharValue()) {
148         return characterLiteralWithSingleQuotes(value.asChar());
149       } else {
150         return value.getValue().toString();
151       }
152     } catch (TypeNotPresentException e) {
153       return e.typeName();
154     }
155   }
156 
characterLiteralWithSingleQuotes(char c)157   public static String characterLiteralWithSingleQuotes(char c) {
158     return "'" + characterLiteralWithoutSingleQuotes(c) + "'";
159   }
160 
161   // TODO(bcorso): Replace with javapoet when fixed: https://github.com/square/javapoet/issues/698.
characterLiteralWithoutSingleQuotes(char c)162   private static String characterLiteralWithoutSingleQuotes(char c) {
163     // see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
164     switch (c) {
165       case '\b': // backspace (BS)
166         return "\\b";
167       case '\t': // horizontal tab (HT)
168         return "\\t";
169       case '\n': // linefeed (LF)
170         return "\\n";
171       case '\f': // form feed (FF)
172         return "\\f";
173       case '\r': // carriage return (CR)
174         return "\\r";
175       case '\"': // double quote (")
176         return "\"";
177       case '\'': // single quote (')
178         return "\\'";
179       case '\\': // backslash (\)
180         return "\\\\";
181       default:
182         return isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
183     }
184   }
185 
XAnnotationValues()186   private XAnnotationValues() {}
187 }
188