1 /* 2 * Copyright (C) 2019 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.kotlin; 18 19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; 20 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 21 22 import androidx.room.compiler.processing.XAnnotation; 23 import androidx.room.compiler.processing.XFieldElement; 24 import androidx.room.compiler.processing.XMethodElement; 25 import androidx.room.compiler.processing.XTypeElement; 26 import com.google.auto.value.AutoValue; 27 import com.google.auto.value.extension.memoized.Memoized; 28 import com.google.common.base.Preconditions; 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.collect.ImmutableSet; 32 import dagger.internal.codegen.extension.DaggerCollectors; 33 import dagger.internal.codegen.javapoet.TypeNames; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Objects; 37 import java.util.Optional; 38 import java.util.function.Function; 39 import javax.annotation.Nullable; 40 import kotlin.Metadata; 41 import kotlinx.metadata.Flag; 42 import kotlinx.metadata.KmClass; 43 import kotlinx.metadata.KmConstructor; 44 import kotlinx.metadata.KmFunction; 45 import kotlinx.metadata.KmProperty; 46 import kotlinx.metadata.jvm.JvmExtensionsKt; 47 import kotlinx.metadata.jvm.JvmFieldSignature; 48 import kotlinx.metadata.jvm.JvmMetadataUtil; 49 import kotlinx.metadata.jvm.JvmMethodSignature; 50 import kotlinx.metadata.jvm.KotlinClassMetadata; 51 52 /** Data class of a TypeElement and its Kotlin metadata. */ 53 @AutoValue 54 abstract class KotlinMetadata { 55 // Kotlin suffix for fields that are for a delegated property. 56 // See: 57 // https://github.com/JetBrains/kotlin/blob/master/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAbi.kt#L32 58 private static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate"; 59 60 // Map that associates field elements with its Kotlin synthetic method for annotations. 61 private final Map<XFieldElement, Optional<MethodForAnnotations>> elementFieldAnnotationMethodMap = 62 new HashMap<>(); 63 64 // Map that associates field elements with its Kotlin getter method. 65 private final Map<XFieldElement, Optional<XMethodElement>> elementFieldGetterMethodMap = 66 new HashMap<>(); 67 typeElement()68 abstract XTypeElement typeElement(); 69 classMetadata()70 abstract ClassMetadata classMetadata(); 71 72 @Memoized methodDescriptors()73 ImmutableMap<String, XMethodElement> methodDescriptors() { 74 return typeElement().getDeclaredMethods().stream() 75 .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity())); 76 } 77 78 /** Gets the synthetic method for annotations of a given field element. */ getSyntheticAnnotationMethod(XFieldElement fieldElement)79 Optional<XMethodElement> getSyntheticAnnotationMethod(XFieldElement fieldElement) { 80 return getAnnotationMethod(fieldElement) 81 .map( 82 methodForAnnotations -> { 83 if (methodForAnnotations == MethodForAnnotations.MISSING) { 84 throw new IllegalStateException( 85 "Method for annotations is missing for " + fieldElement); 86 } 87 return methodForAnnotations.method(); 88 }); 89 } 90 91 /** 92 * Returns true if the synthetic method for annotations is missing. This can occur when inspecting 93 * the Kotlin metadata of a property from another compilation unit. 94 */ 95 boolean isMissingSyntheticAnnotationMethod(XFieldElement fieldElement) { 96 return getAnnotationMethod(fieldElement) 97 .map(methodForAnnotations -> methodForAnnotations == MethodForAnnotations.MISSING) 98 // This can be missing if there was no property annotation at all (e.g. no annotations or 99 // the qualifier is already properly attached to the field). For these cases, it isn't 100 // considered missing since there was no method to look for in the first place. 101 .orElse(false); 102 } 103 104 private Optional<MethodForAnnotations> getAnnotationMethod(XFieldElement fieldElement) { 105 return elementFieldAnnotationMethodMap.computeIfAbsent( 106 fieldElement, this::getAnnotationMethodUncached); 107 } 108 109 private Optional<MethodForAnnotations> getAnnotationMethodUncached(XFieldElement fieldElement) { 110 return findProperty(fieldElement) 111 .methodForAnnotationsSignature() 112 .map( 113 signature -> 114 Optional.ofNullable(methodDescriptors().get(signature)) 115 .map(MethodForAnnotations::create) 116 // The method may be missing across different compilations. 117 // See https://youtrack.jetbrains.com/issue/KT-34684 118 .orElse(MethodForAnnotations.MISSING)); 119 } 120 121 /** Gets the getter method of a given field element corresponding to a property. */ 122 Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) { 123 return elementFieldGetterMethodMap.computeIfAbsent( 124 fieldElement, this::getPropertyGetterUncached); 125 } 126 127 private Optional<XMethodElement> getPropertyGetterUncached(XFieldElement fieldElement) { 128 return findProperty(fieldElement) 129 .getterSignature() 130 .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature))); 131 } 132 133 private PropertyMetadata findProperty(XFieldElement field) { 134 String fieldDescriptor = field.getJvmDescriptor(); 135 if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) { 136 return classMetadata().propertiesByFieldSignature().get(fieldDescriptor); 137 } else { 138 // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124 139 final String propertyName = getPropertyNameFromField(field); 140 return classMetadata().propertiesByFieldSignature().values().stream() 141 .filter(property -> propertyName.contentEquals(property.name())) 142 .collect(DaggerCollectors.onlyElement()); 143 } 144 } 145 146 private static String getPropertyNameFromField(XFieldElement field) { 147 String name = getSimpleName(field); 148 if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) { 149 return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length()); 150 } else { 151 return name; 152 } 153 } 154 155 /** Parse Kotlin class metadata from a given type element. */ 156 static KotlinMetadata from(XTypeElement typeElement) { 157 return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.create(metadataOf(typeElement))); 158 } 159 160 private static KotlinClassMetadata.Class metadataOf(XTypeElement typeElement) { 161 XAnnotation annotationMirror = typeElement.getAnnotation(TypeNames.KOTLIN_METADATA); 162 Preconditions.checkNotNull(annotationMirror); 163 Metadata metadataAnnotation = 164 JvmMetadataUtil.Metadata( 165 annotationMirror.getAsInt("k"), 166 annotationMirror.getAsIntList("mv").stream().mapToInt(Integer::intValue).toArray(), 167 annotationMirror.getAsStringList("d1").toArray(new String[0]), 168 annotationMirror.getAsStringList("d2").toArray(new String[0]), 169 annotationMirror.getAsString("xs"), 170 annotationMirror.getAnnotationValue("pn").hasStringValue() 171 ? annotationMirror.getAsString("pn") 172 : null, 173 annotationMirror.getAnnotationValue("xi").hasIntValue() 174 ? annotationMirror.getAsInt("xi") 175 : null); 176 KotlinClassMetadata metadata = KotlinClassMetadata.read(metadataAnnotation); 177 if (metadata == null) { 178 // Can happen if Kotlin < 1.0 or if metadata version is not supported, i.e. 179 // kotlinx-metadata-jvm is outdated. 180 throw new IllegalStateException( 181 "Unable to read Kotlin metadata due to unsupported metadata version."); 182 } 183 if (metadata instanceof KotlinClassMetadata.Class) { 184 // TODO(danysantiago): If when we need other types of metadata then move to right method. 185 return (KotlinClassMetadata.Class) metadata; 186 } else { 187 throw new IllegalStateException("Unsupported metadata type: " + metadata); 188 } 189 } 190 191 @AutoValue 192 abstract static class ClassMetadata extends BaseMetadata { 193 abstract Optional<String> companionObjectName(); 194 195 abstract ImmutableSet<FunctionMetadata> constructors(); 196 197 abstract ImmutableMap<String, FunctionMetadata> functionsBySignature(); 198 199 abstract ImmutableMap<String, PropertyMetadata> propertiesByFieldSignature(); 200 201 static ClassMetadata create(KotlinClassMetadata.Class metadata) { 202 KmClass kmClass = metadata.toKmClass(); 203 ClassMetadata.Builder builder = 204 ClassMetadata.builder( 205 kmClass.getFlags(), kmClass.getName()); // // SUPPRESS_GET_NAME_CHECK 206 builder.companionObjectName(Optional.ofNullable(kmClass.getCompanionObject())); 207 kmClass.getConstructors().forEach(it -> builder.addConstructor(FunctionMetadata.create(it))); 208 kmClass.getFunctions().forEach(it -> builder.addFunction(FunctionMetadata.create(it))); 209 kmClass.getProperties().forEach(it -> builder.addProperty(PropertyMetadata.create(it))); 210 return builder.build(); 211 } 212 213 private static Builder builder(int flags, String name) { 214 return new AutoValue_KotlinMetadata_ClassMetadata.Builder().flags(flags).name(name); 215 } 216 217 @AutoValue.Builder 218 abstract static class Builder implements BaseMetadata.Builder<Builder> { 219 abstract Builder companionObjectName(Optional<String> companionObjectName); 220 221 abstract ImmutableSet.Builder<FunctionMetadata> constructorsBuilder(); 222 223 abstract ImmutableMap.Builder<String, FunctionMetadata> functionsBySignatureBuilder(); 224 225 abstract ImmutableMap.Builder<String, PropertyMetadata> propertiesByFieldSignatureBuilder(); 226 227 Builder addConstructor(FunctionMetadata constructor) { 228 constructorsBuilder().add(constructor); 229 functionsBySignatureBuilder().put(constructor.signature(), constructor); 230 return this; 231 } 232 233 Builder addFunction(FunctionMetadata function) { 234 functionsBySignatureBuilder().put(function.signature(), function); 235 return this; 236 } 237 238 Builder addProperty(PropertyMetadata property) { 239 if (property.fieldSignature().isPresent()) { 240 propertiesByFieldSignatureBuilder().put(property.fieldSignature().get(), property); 241 } 242 return this; 243 } 244 245 abstract ClassMetadata build(); 246 } 247 } 248 249 @AutoValue 250 abstract static class FunctionMetadata extends BaseMetadata { 251 abstract String signature(); 252 253 abstract ImmutableList<ValueParameterMetadata> parameters(); 254 255 static FunctionMetadata create(KmConstructor metadata) { 256 FunctionMetadata.Builder builder = FunctionMetadata.builder(metadata.getFlags(), "<init>"); 257 metadata 258 .getValueParameters() 259 .forEach( 260 it -> 261 builder.addParameter( 262 ValueParameterMetadata.create( 263 it.getFlags(), it.getName()))); // SUPPRESS_GET_NAME_CHECK 264 builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString()); 265 return builder.build(); 266 } 267 268 static FunctionMetadata create(KmFunction metadata) { 269 FunctionMetadata.Builder builder = 270 FunctionMetadata.builder( 271 metadata.getFlags(), metadata.getName()); // SUPPRESS_GET_NAME_CHECK 272 metadata 273 .getValueParameters() 274 .forEach( 275 it -> 276 builder.addParameter( 277 ValueParameterMetadata.create( 278 it.getFlags(), it.getName()))); // SUPPRESS_GET_NAME_CHECK 279 builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString()); 280 return builder.build(); 281 } 282 283 private static Builder builder(int flags, String name) { 284 return new AutoValue_KotlinMetadata_FunctionMetadata.Builder().flags(flags).name(name); 285 } 286 287 @AutoValue.Builder 288 abstract static class Builder implements BaseMetadata.Builder<Builder> { 289 abstract Builder signature(String signature); 290 291 abstract ImmutableList.Builder<ValueParameterMetadata> parametersBuilder(); 292 293 Builder addParameter(ValueParameterMetadata parameter) { 294 parametersBuilder().add(parameter); 295 return this; 296 } 297 298 abstract FunctionMetadata build(); 299 } 300 } 301 302 @AutoValue 303 abstract static class PropertyMetadata extends BaseMetadata { 304 /** Returns the JVM field descriptor of the backing field of this property. */ 305 abstract Optional<String> fieldSignature(); 306 307 abstract Optional<String> getterSignature(); 308 309 /** Returns JVM method descriptor of the synthetic method for property annotations. */ 310 abstract Optional<String> methodForAnnotationsSignature(); 311 312 static PropertyMetadata create(KmProperty metadata) { 313 PropertyMetadata.Builder builder = 314 PropertyMetadata.builder( 315 metadata.getFlags(), metadata.getName()); // SUPPRESS_GET_NAME_CHECK 316 builder.fieldSignature( 317 Optional.ofNullable(JvmExtensionsKt.getFieldSignature(metadata)) 318 .map(JvmFieldSignature::asString)); 319 builder.getterSignature( 320 Optional.ofNullable(JvmExtensionsKt.getGetterSignature(metadata)) 321 .map(JvmMethodSignature::asString)); 322 builder.methodForAnnotationsSignature( 323 Optional.ofNullable(JvmExtensionsKt.getSyntheticMethodForAnnotations(metadata)) 324 .map(JvmMethodSignature::asString)); 325 return builder.build(); 326 } 327 328 private static Builder builder(int flags, String name) { 329 return new AutoValue_KotlinMetadata_PropertyMetadata.Builder().flags(flags).name(name); 330 } 331 332 @AutoValue.Builder 333 interface Builder extends BaseMetadata.Builder<Builder> { 334 Builder fieldSignature(Optional<String> signature); 335 336 Builder getterSignature(Optional<String> signature); 337 338 Builder methodForAnnotationsSignature(Optional<String> signature); 339 340 PropertyMetadata build(); 341 } 342 } 343 344 @AutoValue 345 abstract static class ValueParameterMetadata extends BaseMetadata { 346 private static ValueParameterMetadata create(int flags, String name) { 347 return new AutoValue_KotlinMetadata_ValueParameterMetadata(flags, name); 348 } 349 } 350 351 abstract static class BaseMetadata { 352 /** Returns the Kotlin metadata flags for this property. */ 353 abstract int flags(); 354 355 /** returns {@code true} if the given flag (e.g. {@link Flag.IS_PRIVATE}) applies. */ 356 boolean flags(Flag flag) { 357 return flag.invoke(flags()); 358 } 359 360 /** Returns the simple name of this property. */ 361 abstract String name(); 362 363 interface Builder<BuilderT> { 364 BuilderT flags(int flags); 365 366 BuilderT name(String name); 367 } 368 } 369 370 @AutoValue 371 abstract static class MethodForAnnotations { 372 static MethodForAnnotations create(XMethodElement method) { 373 return new AutoValue_KotlinMetadata_MethodForAnnotations(method); 374 } 375 376 static final MethodForAnnotations MISSING = MethodForAnnotations.create(null); 377 378 @Nullable 379 abstract XMethodElement method(); 380 } 381 } 382