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