1 /* <lambda>null2 * 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.processor 18 19 import androidx.room.Entity 20 import androidx.room.parser.Collate 21 import androidx.room.parser.SQLTypeAffinity 22 import androidx.room.solver.types.ColumnTypeAdapter 23 import androidx.room.testing.TestInvocation 24 import androidx.room.testing.TestProcessor 25 import androidx.room.vo.Field 26 import com.google.auto.common.MoreElements 27 import com.google.auto.common.MoreTypes 28 import com.google.common.truth.Truth 29 import com.google.testing.compile.CompileTester 30 import com.google.testing.compile.JavaFileObjects 31 import com.google.testing.compile.JavaSourcesSubjectFactory 32 import org.hamcrest.CoreMatchers.`is` 33 import org.hamcrest.MatcherAssert.assertThat 34 import org.junit.Test 35 import org.junit.runner.RunWith 36 import org.junit.runners.JUnit4 37 import org.mockito.Mockito.mock 38 import simpleRun 39 import javax.lang.model.element.Element 40 import javax.lang.model.element.ElementKind 41 import javax.lang.model.type.TypeKind 42 import javax.lang.model.type.TypeMirror 43 44 @Suppress("HasPlatformType") 45 @RunWith(JUnit4::class) 46 class FieldProcessorTest { 47 companion object { 48 const val ENTITY_PREFIX = """ 49 package foo.bar; 50 import androidx.room.*; 51 @Entity 52 abstract class MyEntity { 53 """ 54 const val ENTITY_SUFFIX = "}" 55 val ALL_PRIMITIVES = arrayListOf( 56 TypeKind.INT, 57 TypeKind.BYTE, 58 TypeKind.SHORT, 59 TypeKind.LONG, 60 TypeKind.CHAR, 61 TypeKind.FLOAT, 62 TypeKind.DOUBLE) 63 val ARRAY_CONVERTER = JavaFileObjects.forSourceLines("foo.bar.MyConverter", 64 """ 65 package foo.bar; 66 import androidx.room.*; 67 public class MyConverter { 68 ${ALL_PRIMITIVES.joinToString("\n") { 69 val arrayDef = "${it.name.toLowerCase()}[]" 70 "@TypeConverter public static String" + 71 " arrayIntoString($arrayDef input) { return null;}" + 72 "@TypeConverter public static $arrayDef" + 73 " stringIntoArray${it.name}(String input) { return null;}" 74 }} 75 ${ALL_PRIMITIVES.joinToString("\n") { 76 val arrayDef = "${it.box()}[]" 77 "@TypeConverter public static String" + 78 " arrayIntoString($arrayDef input) { return null;}" + 79 "@TypeConverter public static $arrayDef" + 80 " stringIntoArray${it.name}Boxed(String input) { return null;}" 81 }} 82 } 83 """) 84 85 private fun TypeKind.box(): String { 86 return "java.lang." + when (this) { 87 TypeKind.INT -> "Integer" 88 TypeKind.CHAR -> "Character" 89 else -> this.name.toLowerCase().capitalize() 90 } 91 } 92 93 // these 2 box methods are ugly but makes tests nicer and they are private 94 private fun TypeKind.typeMirror(invocation: TestInvocation): TypeMirror { 95 return invocation.processingEnv.typeUtils.getPrimitiveType(this) 96 } 97 98 private fun TypeKind.affinity(): SQLTypeAffinity { 99 return when (this) { 100 TypeKind.FLOAT, TypeKind.DOUBLE -> SQLTypeAffinity.REAL 101 else -> SQLTypeAffinity.INTEGER 102 } 103 } 104 105 private fun TypeKind.box(invocation: TestInvocation): TypeMirror { 106 return invocation.processingEnv.elementUtils.getTypeElement(box()).asType() 107 } 108 } 109 110 @Test 111 fun primitives() { 112 ALL_PRIMITIVES.forEach { primitive -> 113 singleEntity("${primitive.name.toLowerCase()} x;") { field, invocation -> 114 assertThat(field, `is`( 115 Field(name = "x", 116 type = primitive.typeMirror(invocation), 117 element = field.element, 118 affinity = primitive.affinity() 119 ))) 120 }.compilesWithoutError() 121 } 122 } 123 124 @Test 125 fun boxed() { 126 ALL_PRIMITIVES.forEach { primitive -> 127 singleEntity("${primitive.box()} y;") { field, invocation -> 128 assertThat(field, `is`( 129 Field(name = "y", 130 type = primitive.box(invocation), 131 element = field.element, 132 affinity = primitive.affinity()))) 133 }.compilesWithoutError() 134 } 135 } 136 137 @Test 138 fun columnName() { 139 singleEntity(""" 140 @ColumnInfo(name = "foo") 141 @PrimaryKey 142 int x; 143 """) { field, invocation -> 144 assertThat(field, `is`( 145 Field(name = "x", 146 type = TypeKind.INT.typeMirror(invocation), 147 element = field.element, 148 columnName = "foo", 149 affinity = SQLTypeAffinity.INTEGER))) 150 }.compilesWithoutError() 151 } 152 153 @Test 154 fun indexed() { 155 singleEntity(""" 156 @ColumnInfo(name = "foo", index = true) 157 int x; 158 """) { field, invocation -> 159 assertThat(field, `is`( 160 Field(name = "x", 161 type = TypeKind.INT.typeMirror(invocation), 162 element = field.element, 163 columnName = "foo", 164 affinity = SQLTypeAffinity.INTEGER, 165 indexed = true))) 166 }.compilesWithoutError() 167 } 168 169 @Test 170 fun emptyColumnName() { 171 singleEntity(""" 172 @ColumnInfo(name = "") 173 int x; 174 """) { _, _ -> 175 }.failsToCompile().withErrorContaining(ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY) 176 } 177 178 @Test 179 fun byteArrayWithEnforcedType() { 180 singleEntity("@TypeConverters(foo.bar.MyConverter.class)" + 181 "@ColumnInfo(typeAffinity = ColumnInfo.TEXT) byte[] arr;") { field, invocation -> 182 assertThat(field, `is`(Field(name = "arr", 183 type = invocation.processingEnv.typeUtils.getArrayType( 184 TypeKind.BYTE.typeMirror(invocation)), 185 element = field.element, 186 affinity = SQLTypeAffinity.TEXT))) 187 assertThat((field.cursorValueReader as? ColumnTypeAdapter)?.typeAffinity, 188 `is`(SQLTypeAffinity.TEXT)) 189 }.compilesWithoutError() 190 } 191 192 @Test 193 fun primitiveArray() { 194 ALL_PRIMITIVES.forEach { primitive -> 195 singleEntity("@TypeConverters(foo.bar.MyConverter.class) " + 196 "${primitive.toString().toLowerCase()}[] arr;") { field, invocation -> 197 assertThat(field, `is`( 198 Field(name = "arr", 199 type = invocation.processingEnv.typeUtils.getArrayType( 200 primitive.typeMirror(invocation)), 201 element = field.element, 202 affinity = if (primitive == TypeKind.BYTE) { 203 SQLTypeAffinity.BLOB 204 } else { 205 SQLTypeAffinity.TEXT 206 }))) 207 }.compilesWithoutError() 208 } 209 } 210 211 @Test 212 fun boxedArray() { 213 ALL_PRIMITIVES.forEach { primitive -> 214 singleEntity("@TypeConverters(foo.bar.MyConverter.class) " + 215 "${primitive.box()}[] arr;") { field, invocation -> 216 assertThat(field, `is`( 217 Field(name = "arr", 218 type = invocation.processingEnv.typeUtils.getArrayType( 219 primitive.box(invocation)), 220 element = field.element, 221 affinity = SQLTypeAffinity.TEXT))) 222 }.compilesWithoutError() 223 } 224 } 225 226 @Test 227 fun generic() { 228 singleEntity(""" 229 static class BaseClass<T> { 230 T item; 231 } 232 @Entity 233 static class Extending extends BaseClass<java.lang.Integer> { 234 } 235 """) { field, invocation -> 236 assertThat(field, `is`(Field(name = "item", 237 type = TypeKind.INT.box(invocation), 238 element = field.element, 239 affinity = SQLTypeAffinity.INTEGER))) 240 }.compilesWithoutError() 241 } 242 243 @Test 244 fun unboundGeneric() { 245 singleEntity(""" 246 @Entity 247 static class BaseClass<T> { 248 T item; 249 } 250 """) { _, _ -> }.failsToCompile() 251 .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS) 252 } 253 254 @Test 255 fun nameVariations() { 256 simpleRun { 257 assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it), 258 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x"))) 259 assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it), 260 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x"))) 261 assertThat(Field(mock(Element::class.java), "xAll", 262 TypeKind.BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER) 263 .nameWithVariations, `is`(arrayListOf("xAll"))) 264 } 265 } 266 267 @Test 268 fun nameVariations_is() { 269 val elm = mock(Element::class.java) 270 simpleRun { 271 assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it), 272 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX", "x"))) 273 assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it), 274 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX"))) 275 assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it), 276 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("is"))) 277 assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it), 278 SQLTypeAffinity.INTEGER).nameWithVariations, 279 `is`(arrayListOf("isAllItems", "allItems"))) 280 } 281 } 282 283 @Test 284 fun nameVariations_has() { 285 val elm = mock(Element::class.java) 286 simpleRun { 287 assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it), 288 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX", "x"))) 289 assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it), 290 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX"))) 291 assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it), 292 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("has"))) 293 assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it), 294 SQLTypeAffinity.INTEGER).nameWithVariations, 295 `is`(arrayListOf("hasAllItems", "allItems"))) 296 } 297 } 298 299 @Test 300 fun nameVariations_m() { 301 val elm = mock(Element::class.java) 302 simpleRun { 303 assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it), 304 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mall"))) 305 assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it), 306 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mallVars"))) 307 assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it), 308 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mAll", "all"))) 309 assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it), 310 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("m"))) 311 assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it), 312 SQLTypeAffinity.INTEGER).nameWithVariations, 313 `is`(arrayListOf("mallItems"))) 314 assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it), 315 SQLTypeAffinity.INTEGER).nameWithVariations, 316 `is`(arrayListOf("mAllItems", "allItems"))) 317 } 318 } 319 320 @Test 321 fun nameVariations_underscore() { 322 val elm = mock(Element::class.java) 323 simpleRun { 324 assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it), 325 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_all", "all"))) 326 assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it), 327 SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_"))) 328 assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it), 329 SQLTypeAffinity.INTEGER).nameWithVariations, 330 `is`(arrayListOf("_allItems", "allItems"))) 331 } 332 } 333 334 @Test 335 fun collate() { 336 Collate.values().forEach { collate -> 337 singleEntity(""" 338 @PrimaryKey 339 @ColumnInfo(collate = ColumnInfo.${collate.name}) 340 String code; 341 """) { field, invocation -> 342 assertThat(field, `is`( 343 Field(name = "code", 344 type = invocation.context.COMMON_TYPES.STRING, 345 element = field.element, 346 columnName = "code", 347 collate = collate, 348 affinity = SQLTypeAffinity.TEXT))) 349 }.compilesWithoutError() 350 } 351 } 352 353 fun singleEntity(vararg input: String, handler: (Field, invocation: TestInvocation) -> Unit): 354 CompileTester { 355 return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources()) 356 .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity", 357 ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX 358 ), ARRAY_CONVERTER)) 359 .processedWith(TestProcessor.builder() 360 .forAnnotations(androidx.room.Entity::class) 361 .nextRunHandler { invocation -> 362 val (owner, field) = invocation.roundEnv 363 .getElementsAnnotatedWith(Entity::class.java) 364 .map { 365 Pair(it, invocation.processingEnv.elementUtils 366 .getAllMembers(MoreElements.asType(it)) 367 .firstOrNull { it.kind == ElementKind.FIELD }) 368 } 369 .first { it.second != null } 370 val entityContext = 371 EntityProcessor(invocation.context, MoreElements.asType(owner)) 372 .context 373 val parser = FieldProcessor( 374 baseContext = entityContext, 375 containing = MoreTypes.asDeclared(owner.asType()), 376 element = field!!, 377 bindingScope = FieldProcessor.BindingScope.TWO_WAY, 378 fieldParent = null) 379 handler(parser.process(), invocation) 380 true 381 } 382 .build()) 383 } 384 } 385