1 /*
2  * Copyright (C) 2016 The Android Open Source Project
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 androidx.room.writer
18 
19 import androidx.room.RoomProcessor
20 import androidx.room.ext.S
21 import androidx.room.ext.typeName
22 import androidx.room.solver.CodeGenScope.Companion.CLASS_PROPERTY_PREFIX
23 import com.squareup.javapoet.AnnotationSpec
24 import com.squareup.javapoet.ClassName
25 import com.squareup.javapoet.FieldSpec
26 import com.squareup.javapoet.JavaFile
27 import com.squareup.javapoet.MethodSpec
28 import com.squareup.javapoet.TypeName
29 import com.squareup.javapoet.TypeSpec
30 import javax.annotation.processing.ProcessingEnvironment
31 
32 /**
33  * Base class for all writers that can produce a class.
34  */
35 abstract class ClassWriter(private val className: ClassName) {
36     private val sharedFieldSpecs = mutableMapOf<String, FieldSpec>()
37     private val sharedMethodSpecs = mutableMapOf<String, MethodSpec>()
38     private val sharedFieldNames = mutableSetOf<String>()
39     private val sharedMethodNames = mutableSetOf<String>()
40 
createTypeSpecBuildernull41     abstract fun createTypeSpecBuilder(): TypeSpec.Builder
42 
43     fun write(processingEnv: ProcessingEnvironment) {
44         val builder = createTypeSpecBuilder()
45         sharedFieldSpecs.values.forEach { builder.addField(it) }
46         sharedMethodSpecs.values.forEach { builder.addMethod(it) }
47         addGeneratedAnnotationIfAvailable(builder, processingEnv)
48         addSuppressUnchecked(builder)
49         JavaFile.builder(className.packageName(), builder.build())
50                 .build()
51                 .writeTo(processingEnv.filer)
52     }
53 
addSuppressUncheckednull54     private fun addSuppressUnchecked(builder: TypeSpec.Builder) {
55         val suppressSpec = AnnotationSpec.builder(SuppressWarnings::class.typeName()).addMember(
56                 "value",
57                 S,
58                 "unchecked"
59         ).build()
60         builder.addAnnotation(suppressSpec)
61     }
62 
addGeneratedAnnotationIfAvailablenull63     private fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
64                                                   processingEnv: ProcessingEnvironment) {
65         val generatedAnnotationAvailable = processingEnv
66                 .elementUtils
67                 .getTypeElement("$GENERATED_PACKAGE.$GENERATED_NAME") != null
68         if (generatedAnnotationAvailable) {
69             val className = ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)
70             val generatedAnnotationSpec =
71                     AnnotationSpec.builder(className).addMember(
72                             "value",
73                             S,
74                             RoomProcessor::class.java.canonicalName).build()
75             adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
76         }
77     }
78 
makeUniquenull79     private fun makeUnique(set: MutableSet<String>, value: String): String {
80         if (!value.startsWith(CLASS_PROPERTY_PREFIX)) {
81             return makeUnique(set, "$CLASS_PROPERTY_PREFIX$value")
82         }
83         if (set.add(value)) {
84             return value
85         }
86         var index = 1
87         while (true) {
88             if (set.add("${value}_$index")) {
89                 return "${value}_$index"
90             }
91             index++
92         }
93     }
94 
getOrCreateFieldnull95     fun getOrCreateField(sharedField: SharedFieldSpec): FieldSpec {
96         return sharedFieldSpecs.getOrPut(sharedField.getUniqueKey(), {
97             sharedField.build(this, makeUnique(sharedFieldNames, sharedField.baseName))
98         })
99     }
100 
getOrCreateMethodnull101     fun getOrCreateMethod(sharedMethod: SharedMethodSpec): MethodSpec {
102         return sharedMethodSpecs.getOrPut(sharedMethod.getUniqueKey(), {
103             sharedMethod.build(this, makeUnique(sharedMethodNames, sharedMethod.baseName))
104         })
105     }
106 
107     abstract class SharedFieldSpec(val baseName: String, val type: TypeName) {
108 
getUniqueKeynull109         abstract fun getUniqueKey(): String
110 
111         abstract fun prepare(writer: ClassWriter, builder: FieldSpec.Builder)
112 
113         fun build(classWriter: ClassWriter, name: String): FieldSpec {
114             val builder = FieldSpec.builder(type, name)
115             prepare(classWriter, builder)
116             return builder.build()
117         }
118     }
119 
120     abstract class SharedMethodSpec(val baseName: String) {
121 
getUniqueKeynull122         abstract fun getUniqueKey(): String
123         abstract fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder)
124 
125         fun build(writer: ClassWriter, name: String): MethodSpec {
126             val builder = MethodSpec.methodBuilder(name)
127             prepare(name, writer, builder)
128             return builder.build()
129         }
130     }
131 
132     companion object {
133         private const val GENERATED_PACKAGE = "javax.annotation"
134         private const val GENERATED_NAME = "Generated"
135     }
136 }
137