1 /*
2  * Copyright (C) 2021 Square, Inc.
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  * https://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 package com.squareup.kotlinpoet.metadata.specs
17 
18 import com.squareup.kotlinpoet.AnnotationSpec
19 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.GET
20 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
21 import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
22 
23 /**
24  * Represents relevant information on a property used for [ClassInspector]. Should only be
25  * associated with properties of a [ClassData].
26  *
27  * @param annotations declared annotations on this property.
28  * @property fieldData associated [FieldData] with this property, if any.
29  * @property getterData associated getter (as [MethodData]) with this property, if any.
30  * @property setterData associated setter (as [MethodData]) with this property, if any.
31  * @property isJvmField indicates if this property should be treated as a jvm field.
32  */
33 public data class PropertyData(
34   private val annotations: List<AnnotationSpec>,
35   val fieldData: FieldData?,
36   val getterData: MethodData?,
37   val setterData: MethodData?,
38   val isJvmField: Boolean,
39 ) {
40   /** Indicates if this property overrides another from a supertype. */
41   val isOverride: Boolean = (getterData?.isOverride ?: false) || (setterData?.isOverride ?: false)
42 
43   /**
44    * A collection of all annotations on this property including declared ones and any derived from
45    * [fieldData], [getterData], [setterData], and [isJvmField].
46    */
<lambda>null47   val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
48     // Don't add annotations that are already defined on the parent
49     val higherScopedAnnotations = annotations.associateBy { it.typeName }
50     val fieldAnnotations = fieldData?.allAnnotations.orEmpty()
51       .filterNot { it.typeName in higherScopedAnnotations }
52       .associateByTo(LinkedHashMap()) { it.typeName }
53     val getterAnnotations = getterData?.allAnnotations(GET).orEmpty()
54       .filterNot { it.typeName in higherScopedAnnotations }
55       .associateByTo(LinkedHashMap()) { it.typeName }
56 
57     val finalTopAnnotations = annotations.toMutableList()
58 
59     // If this is a val, and annotation is on both getter and field, we can move it to just the
60     // regular annotations
61     if (setterData == null && !isJvmField) {
62       val sharedAnnotations = getterAnnotations.keys.intersect(fieldAnnotations.keys)
63       for (sharedAnnotation in sharedAnnotations) {
64         // Add it to the top-level annotations without a site-target
65         finalTopAnnotations += getterAnnotations.getValue(sharedAnnotation).toBuilder()
66           .useSiteTarget(null)
67           .build()
68 
69         // Remove from field and getter
70         fieldAnnotations.remove(sharedAnnotation)
71         getterAnnotations.remove(sharedAnnotation)
72       }
73     }
74 
75     addAll(finalTopAnnotations)
76     addAll(fieldAnnotations.values)
77     addAll(getterAnnotations.values)
78     addAll(
79       setterData?.allAnnotations(SET).orEmpty()
80         .filterNot { it.typeName in higherScopedAnnotations },
81     )
82     if (isJvmField) {
83       add(ClassInspectorUtil.JVM_FIELD_SPEC)
84     }
85   }
86 }
87