Field parser, run lint for test files
Bug: 32342709
Test: FieldParserTest.kt
Change-Id: I6e1e486eeb11bb4ae0844c3982401217c5198811
diff --git a/flatfoot-common/init.gradle b/flatfoot-common/init.gradle
index cbbc1b6..61f160b 100644
--- a/flatfoot-common/init.gradle
+++ b/flatfoot-common/init.gradle
@@ -55,6 +55,7 @@
def kotlinCheckstyle = project.tasks.create(name : 'checkstyleKotlin', type: Checkstyle) {
configFile rootProject.file('../flatfoot-common/kotlin-checkstyle.xml')
source project.sourceSets.main.allJava
+ source project.sourceSets.test.allJava
ignoreFailures false
showViolations true
include '**/*.kt'
@@ -64,14 +65,15 @@
project.tasks.findByName("check").dependsOn(kotlinCheckstyle)
// poor man's line length check
def lineCheck = project.tasks.create(name : "lineLengthCheck") {
- project.sourceSets.main.allJava.getSourceDirectories().each { sourceDir ->
- fileTree(dir : sourceDir, include : "**/*.kt").each{ file ->
- file.readLines().eachWithIndex { line, index ->
- if (line.size() > 100) {
- throw new Exception("line too long:\nfile: $file line:$index line: $line")
- }
- }
- }
+ (project.sourceSets.main.allJava.getSourceDirectories() +
+ project.sourceSets.test.allJava.getSourceDirectories()).each { sourceDir ->
+ fileTree(dir : sourceDir, include : "**/*.kt").each{ file ->
+ file.readLines().eachWithIndex { line, index ->
+ if (line.size() > 100) {
+ throw new Exception("line too long:\nfile: $file line:$index line: $line")
+ }
+ }
+ }
}
}
kotlinCheckstyle.dependsOn(lineCheck)
@@ -80,7 +82,7 @@
def createAndroidCheckstyle(Project project) {
def androidCheckstyle = project.tasks.create(name : 'checkstyleAndroid', type: Checkstyle) {
configFile rootProject.file('../../../development/tools/checkstyle/android-style.xml')
- source 'src/main/java'
+ source project.sourceSets.main.allJava
ignoreFailures false
showViolations true
include '**/*.java'
diff --git a/room/common/src/main/java/com/android/support/room/PrimaryKey.java b/room/common/src/main/java/com/android/support/room/PrimaryKey.java
new file mode 100644
index 0000000..49798cd
--- /dev/null
+++ b/room/common/src/main/java/com/android/support/room/PrimaryKey.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface PrimaryKey {
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt b/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt
index 532cd15..05d0394 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt
@@ -17,6 +17,10 @@
package com.android.support.room.preconditions
import com.android.support.room.errors.ElementBoundException
+import com.android.support.room.processor.ProcessorErrors
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
import javax.lang.model.element.Element
/**
@@ -28,4 +32,13 @@
throw ElementBoundException(element, String.format(errorMsg, args))
}
}
+
+ fun assertNotUnbound(typeName: TypeName, element: Element, errorMsg : String,
+ vararg args : Any) {
+ // TODO support bounds cases like <T extends Foo> T bar()
+ Checks.check(typeName !is TypeVariableName, element, errorMsg, args)
+ if (typeName is ParameterizedTypeName) {
+ typeName.typeArguments.forEach { assertNotUnbound(it, element, errorMsg, args) }
+ }
+ }
}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt
new file mode 100644
index 0000000..d02f25a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.room.processor
+
+import com.android.support.room.PrimaryKey
+import com.android.support.room.preconditions.Checks
+import com.android.support.room.vo.Field
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.TypeName
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.Element
+import javax.lang.model.type.DeclaredType
+
+class FieldParser(val roundEnv: RoundEnvironment,
+ val processingEnvironment: ProcessingEnvironment) {
+ fun parse(containing : DeclaredType, element : Element) : Field {
+ val member = processingEnvironment.typeUtils.asMemberOf(containing, element)
+ val type = TypeName.get(member)
+ Checks.assertNotUnbound(type, element,
+ ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
+ return Field(name = element.simpleName.toString(),
+ type = type,
+ primaryKey = MoreElements.isAnnotationPresent(element, PrimaryKey::class.java))
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
index 1f53c99..c2fe0e4 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
@@ -23,4 +23,5 @@
val CANNOT_RESOLVE_RETURN_TYPE = "Cannot resolve return type for %s"
val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS = "Cannot use unbound generics in query " +
"methods. It must be bound to a type through base Dao class."
+ val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt
index 0b96331..838fa87 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt
@@ -48,20 +48,12 @@
val returnTypeName = TypeName.get(executableType.returnType)
- assertNoUnboundParameter(returnTypeName, executableElement)
+ Checks.assertNotUnbound(returnTypeName, executableElement,
+ ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
return QueryMethod(
query,
executableElement.simpleName.toString(),
returnTypeName,
executableElement.parameters.map { parameterParser.parse(containing, it) })
}
-
- fun assertNoUnboundParameter(typeName: TypeName, element: Element) {
- // TODO support bounds cases like <T extends Foo> T bar()
- Checks.check(typeName !is TypeVariableName, element,
- ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
- if (typeName is ParameterizedTypeName) {
- typeName.typeArguments.forEach { assertNoUnboundParameter(it, element) }
- }
- }
}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
new file mode 100644
index 0000000..100d26b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.room.vo
+
+import com.squareup.javapoet.TypeName
+
+data class Entity(val type: TypeName)
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
new file mode 100644
index 0000000..17488ca
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.room.vo
+
+import com.squareup.javapoet.TypeName
+
+data class Field(val name : String, val type: TypeName, val primaryKey : Boolean)
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Parameter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Parameter.kt
index a71338d..f9c1a70 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/Parameter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Parameter.kt
@@ -21,4 +21,4 @@
/**
* Holds the parameter for a {@link QueryMethod}.
*/
-class Parameter(val name: String, val type: TypeName)
+data class Parameter(val name: String, val type: TypeName)
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt
index 38ef6a1..952f026 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt
@@ -23,5 +23,5 @@
* A class that holds information about a QueryMethod.
* It is self sufficient and must have all generics etc resolved once created.
*/
-class QueryMethod(val query: ParsedQuery, val name: String, val returnType: TypeName,
+data class QueryMethod(val query: ParsedQuery, val name: String, val returnType: TypeName,
val parameters: List<Parameter>)
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt
new file mode 100644
index 0000000..07d6f26
--- /dev/null
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.room.processor
+
+import com.android.support.room.Entity
+import com.android.support.room.testing.TestProcessor
+import com.android.support.room.vo.Field
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.ElementKind
+
+@RunWith(JUnit4::class)
+class FieldParserTest {
+ companion object {
+ const val ENTITY_PREFIX = """
+ package foo.bar;
+ import com.android.support.room.*;
+ @Entity
+ abstract class MyEntity {
+ """
+ const val ENTITY_SUFFIX = "}"
+ val ALL_PRIMITIVES = arrayListOf(
+ TypeName.BOOLEAN,
+ TypeName.BYTE,
+ TypeName.SHORT,
+ TypeName.INT,
+ TypeName.LONG,
+ TypeName.CHAR,
+ TypeName.FLOAT,
+ TypeName.DOUBLE)
+ }
+
+ @Test
+ fun primitives() {
+ ALL_PRIMITIVES.forEach { primitive ->
+ singleEntity("$primitive x;") { field ->
+ assertThat(field.name, `is`("x"))
+ assertThat(field.type, `is`(primitive))
+ assertThat(field.primaryKey, `is`(false))
+ }.compilesWithoutError()
+ }
+ }
+
+ @Test
+ fun boxed() {
+ ALL_PRIMITIVES.forEach { primitive ->
+ singleEntity("${primitive.box()} y;") { field ->
+ assertThat(field.name, `is`("y"))
+ assertThat(field.type, `is`(primitive.box()))
+ assertThat(field.primaryKey, `is`(false))
+ }.compilesWithoutError()
+ }
+ }
+
+ @Test
+ fun primaryKey() {
+ singleEntity("""
+ @PrimaryKey
+ int x;
+ """) { field ->
+ assertThat(field, `is`(
+ Field(name = "x", type = TypeName.INT, primaryKey = true)
+ ))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun primitiveArray() {
+ ALL_PRIMITIVES.forEach { primitive ->
+ singleEntity("$primitive[] arr;") { field ->
+ assertThat(field, `is`(
+ Field(name = "arr", type = ArrayTypeName.of(primitive), primaryKey = false)
+ ))
+ }.compilesWithoutError()
+ }
+ }
+
+ @Test
+ fun boxedArray() {
+ ALL_PRIMITIVES.forEach { primitive ->
+ singleEntity("${primitive.box()}[] arr;") { field ->
+ assertThat(field, `is`(
+ Field(name = "arr", type = ArrayTypeName.of(primitive.box()),
+ primaryKey = false)
+ ))
+ }.compilesWithoutError()
+ }
+ }
+
+ @Test
+ fun generic() {
+ singleEntity("""
+ static class BaseClass<T> {
+ T item;
+ }
+ @Entity
+ static class Extending extends BaseClass<java.lang.Integer> {
+ }
+ """) { field ->
+ assertThat(field, `is`(Field(name = "item",
+ type = TypeName.INT.box(),
+ primaryKey = false)))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun unboundGeneric() {
+ singleEntity("""
+ @Entity
+ static class BaseClass<T> {
+ T item;
+ }
+ """) {}.failsToCompile()
+ .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
+ }
+
+ fun singleEntity(vararg input: String, handler: (Field) -> Unit): CompileTester {
+ return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
+ .that(JavaFileObjects.forSourceString("foo.bar.MyEntity",
+ ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
+ ))
+ .processedWith(TestProcessor.builder()
+ .forAnnotations(com.android.support.room.Entity::class)
+ .nextRunHandler { invocation ->
+ val (owner, field) = invocation.roundEnv
+ .getElementsAnnotatedWith(Entity::class.java)
+ .map {
+ Pair(it, invocation.processingEnv.elementUtils
+ .getAllMembers(MoreElements.asType(it))
+ .firstOrNull { it.kind == ElementKind.FIELD })
+ }
+ .first { it.second != null }
+ val parser = FieldParser(invocation.roundEnv, invocation.processingEnv)
+ handler(parser.parse(
+ MoreTypes.asDeclared(owner.asType()), field!!))
+ true
+ }
+ .build())
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryParserTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryParserTest.kt
index 9a53034..23cfffa 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryParserTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryParserTest.kt
@@ -36,6 +36,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@RunWith(JUnit4::class)
class QueryParserTest {
companion object {
@@ -132,26 +133,32 @@
static abstract class ExtendingModel extends BaseModel<Integer> {
}
""") { parsedQuery ->
- assertThat(parsedQuery.parameters.first().type, `is`(ClassName.get(Integer::class.java) as TypeName))
+ assertThat(parsedQuery.parameters.first().type,
+ `is`(ClassName.get(Integer::class.java) as TypeName))
}.compilesWithoutError()
}
- fun singleQueryMethod(vararg methods: String,
+ fun singleQueryMethod(vararg input: String,
handler: (QueryMethod) -> Unit):
CompileTester {
return assertAbout(JavaSourceSubjectFactory.javaSource())
.that(JavaFileObjects.forSourceString("foo.bar.MyClass",
- DAO_PREFIX + methods.joinToString("\n") + DAO_SUFFIX
-
+ DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
))
.processedWith(TestProcessor.builder()
.forAnnotations(Query::class, Dao::class)
.nextRunHandler { invocation ->
- val (owner, methods) = invocation.roundEnv.getElementsAnnotatedWith(Dao::class.java)
+ val (owner, methods) = invocation.roundEnv
+ .getElementsAnnotatedWith(Dao::class.java)
.map {
- Pair(it, invocation.processingEnv.elementUtils.getAllMembers(MoreElements.asType(it))
- .filter { MoreElements.isAnnotationPresent(it, Query::class.java) }
+ Pair(it,
+ invocation.processingEnv.elementUtils
+ .getAllMembers(MoreElements.asType(it))
+ .filter {
+ MoreElements.isAnnotationPresent(it,
+ Query::class.java)
+ }
)
}.filter { it.second.isNotEmpty() }.first()
val parser = QueryParser(invocation.roundEnv, invocation.processingEnv)
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt b/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt
index 7713685..6496e7a 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt
@@ -30,9 +30,11 @@
class TestProcessor(val handlers: List<(TestInvocation) -> Boolean>,
val annotations: MutableSet<String>) : AbstractProcessor() {
var count = 0
- override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
+ override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment)
+ : Boolean {
try {
- return handlers.getOrNull(count++)?.invoke(TestInvocation(processingEnv, annotations, roundEnv)) ?: true
+ return handlers.getOrNull(count++)?.invoke(
+ TestInvocation(processingEnv, annotations, roundEnv)) ?: true
} catch (elmBound: ElementBoundException) {
processingEnv.messager.printMessage(ERROR, elmBound.msg, elmBound.element)
}