TypeConverters

Tihs CL introduces user defined type converters, which are simple
methods annotated with @TypeConverter.

Each type converter is a custom method that receives 1 parameter
and returns 1 value. The container class for the converter must be
passed into a @TypeConverters annotation to be picked up.
Developer can define these in multiple places depending on the scope
they want to use it.

With this CL, I also migrated @ColumnName annotation into @ColumnInfo
annotation so that we can specify more values. This CL introduces
type affinity field in ColumnInfo. Developers can now specify type
affinity to help the resolution logic to pick the right converter.

Bug: 34610784
Bug: 32342709
Test: CustomConverterProcessorTest.kt, FieldProcessorTest.kt
Test: CustomTypeConverterResolutionTest.kt
Change-Id: I117e4a194e8c93bee58f06a43d688ae04360b08e
diff --git a/room/common/src/main/java/com/android/support/room/ColumnInfo.java b/room/common/src/main/java/com/android/support/room/ColumnInfo.java
new file mode 100644
index 0000000..6a0716c
--- /dev/null
+++ b/room/common/src/main/java/com/android/support/room/ColumnInfo.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.support.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Sets the column name of an entity field.
+ * <p>
+ * By default, Room uses the field name as the column name in the database. You can override this
+ * behavior by using this annotation on an Entity field.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface ColumnInfo {
+    /**
+     * Name of the column in the database. Defaults to the field name if not set.
+     *
+     * @return Name of the column in the database.
+     */
+    String name() default INHERIT_FIELD_NAME;
+
+    /**
+     * The type affinity for the column, which will be used when constructing the database.
+     * <p>
+     * If it is not specified, Room resolves it based on the field's type and available
+     * TypeConverters.
+     *
+     * @return The type affinity of the column.
+     */
+    @SQLiteTypeAffinity int affinity() default UNDEFINED;
+
+    /**
+     * Default value for name. If used, Room will use the field name as the column name.
+     */
+    String INHERIT_FIELD_NAME = "[field-name]";
+
+    /**
+     * Undefined type affinity. Will be resolved based on the type.
+     */
+    int UNDEFINED = 1;
+
+    /**
+     * Column affinity constant for strings.
+     */
+    int TEXT = 2;
+    /**
+     * Column affinity constant for integers or booleans.
+     */
+    int INTEGER = 3;
+    /**
+     * Column affinity constant for floats or doubles.
+     */
+    int REAL = 4;
+    /**
+     * Column affinity constant for binary data.
+     */
+    int BLOB = 5;
+
+    /**
+     * The SQLite column type for this field.
+     */
+    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
+    @interface SQLiteTypeAffinity {
+    }
+}
diff --git a/room/common/src/main/java/com/android/support/room/ColumnName.java b/room/common/src/main/java/com/android/support/room/ColumnName.java
deleted file mode 100644
index 6bc5698..0000000
--- a/room/common/src/main/java/com/android/support/room/ColumnName.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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;
-
-/**
- * Sets the column name of an entity field.
- * <p>
- * By default, Room uses the field name as the column name in the database. You can override this
- * behavior by using this annotation on an Entity field.
- */
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.SOURCE)
-public @interface ColumnName {
-    /**
-     * Name of the column in the database. Defaults to the field name if not set.
-     *
-     * @return Name of the column in the database.
-     */
-    String value();
-}
diff --git a/room/common/src/main/java/com/android/support/room/TypeConverter.java b/room/common/src/main/java/com/android/support/room/TypeConverter.java
new file mode 100644
index 0000000..4b47daf
--- /dev/null
+++ b/room/common/src/main/java/com/android/support/room/TypeConverter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Marks a method as a type converter. A class can have as many @Converter methods as it needs.
+ * <p>
+ * Each converter method should receive 1 parameter and have non-void return type.
+ *
+ * <pre>
+ * // example converter for java.util.Date
+ * public static class Converters {
+ *    {@literal @}TypeConverter
+ *    public Date fromTimestamp(Long value) {
+ *        return value == null ? null : new Date(value);
+ *    }
+ *
+ *    {@literal @}TypeConverter
+ *    public Long dateToTimestamp(Date date) {
+ *        if (date == null) {
+ *            return null;
+ *        } else {
+ *            return date.getTime();
+ *        }
+ *    }
+ *}
+ * </pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.SOURCE)
+public @interface TypeConverter {
+}
diff --git a/room/common/src/main/java/com/android/support/room/TypeConverters.java b/room/common/src/main/java/com/android/support/room/TypeConverters.java
new file mode 100644
index 0000000..9fb38a7
--- /dev/null
+++ b/room/common/src/main/java/com/android/support/room/TypeConverters.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Specifies additional type converters that Room can use. The TypeConverter is added to the scope
+ * of the element so if you put it on a class / interface, all methods / fields in that class will
+ * be able to use the converters.
+ * <ul>
+ * <li>If you put it on a @Database, all Daos and Entities in that database will be able to use it.
+ * <li>If you put it on a @Dao, all methods in the Dao will be able to use it.
+ * <li>If you put it on an @Entity, all fields of the Entity will be able to use it.
+ * <li>If you put it on a Pojo, all fields of the Pojo will be able to use it.
+ * <li>If you put it on a Field, only that field will be able to use it.
+ * <li>If you put it on a Dao method, all parameters of the method will be able to use it.
+ * <li>If you put it on a Dao method parameter, just that field will be able to use it.
+ *
+ * @see TypeConverter
+ */
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.SOURCE)
+public @interface TypeConverters {
+    /**
+     * The list of type converter classes. If converter methods are not static, Room will create
+     * and instance of these classes.
+     * @return The list of classes that contains the converter methods.
+     */
+    Class<?>[] value();
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/RoomProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/RoomProcessor.kt
index 9d1a496..98bb024 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/RoomProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/RoomProcessor.kt
@@ -18,11 +18,9 @@
 
 import com.android.support.room.processor.Context
 import com.android.support.room.processor.DatabaseProcessor
-import com.android.support.room.processor.EntityProcessor
 import com.android.support.room.vo.DaoMethod
 import com.android.support.room.writer.DaoWriter
 import com.android.support.room.writer.DatabaseWriter
-import com.android.support.room.writer.EntityCursorConverterWriter
 import com.google.auto.common.BasicAnnotationProcessor
 import com.google.auto.common.MoreElements
 import com.google.common.collect.SetMultimap
@@ -37,8 +35,7 @@
 class RoomProcessor : BasicAnnotationProcessor() {
     override fun initSteps(): MutableIterable<ProcessingStep>? {
         val context = Context(processingEnv)
-        return arrayListOf(EntityProcessingStep(context),
-                DatabaseProcessingStep(context))
+        return arrayListOf(DatabaseProcessingStep(context))
     }
 
     class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
@@ -64,7 +61,7 @@
         }
 
         override fun annotations(): MutableSet<out Class<out Annotation>> {
-            return mutableSetOf(Database::class.java, Dao::class.java)
+            return mutableSetOf(Database::class.java, Dao::class.java, Entity::class.java)
         }
 
         /**
@@ -98,23 +95,5 @@
         }
     }
 
-    class EntityProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
-        override fun process(elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>)
-                : MutableSet<Element> {
-            elementsByAnnotation[Entity::class.java]
-                    ?.map {
-                        EntityProcessor(context, MoreElements.asType(it)).process()
-                    }
-                    ?.forEach {
-                        EntityCursorConverterWriter(it).write(context.processingEnv)
-                    }
-            return mutableSetOf()
-        }
-
-        override fun annotations(): MutableSet<out Class<out Annotation>> {
-            return mutableSetOf(Entity::class.java)
-        }
-    }
-
     abstract class ContextBoundProcessingStep(val context: Context) : ProcessingStep
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt
index bea9a81..87b0ff4 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
+@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+
 package com.android.support.room.ext
 
 import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.AnnotationValue
 import javax.lang.model.element.Element
 import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
 import kotlin.reflect.KClass
 
 fun Element.hasAnyOf(vararg modifiers: Modifier) : Boolean {
@@ -35,3 +41,46 @@
 fun Element.hasAllOf(vararg klasses : KClass<out Annotation>) : Boolean {
     return !klasses.any { !hasAnnotation(it) }
 }
+
+// code below taken from dagger2
+// compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
+private val TO_LIST_OF_TYPES = object
+    : SimpleAnnotationValueVisitor6<List<TypeMirror>, Void?>() {
+    override fun visitArray(values: MutableList<out AnnotationValue>?, p: Void?)
+            : List<TypeMirror> {
+        return values?.map {
+            val tmp = TO_TYPE.visit(it)
+            tmp
+        }?.filterNotNull() ?: emptyList()
+    }
+
+
+    override fun defaultAction(o: Any?, p: Void?): List<TypeMirror>? {
+        return emptyList()
+    }
+}
+
+private val TO_TYPE = object : SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
+
+    override fun visitType(t: TypeMirror, p: Void?): TypeMirror {
+        return t
+    }
+
+    override fun defaultAction(o: Any?, p: Void?): TypeMirror {
+        throw TypeNotPresentException(o!!.toString(), null)
+    }
+}
+
+fun AnnotationValue.toListOfClassTypes(): List<TypeMirror> {
+    return TO_LIST_OF_TYPES.visit(this)
+}
+
+fun AnnotationValue.toClassType(): TypeMirror? {
+    return TO_TYPE.visit(this)
+}
+
+fun TypeMirror.isCollection() : Boolean {
+    return MoreTypes.isType(this)
+            && (MoreTypes.isTypeOf(java.util.List::class.java, this)
+            || MoreTypes.isTypeOf(java.util.Set::class.java, this))
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/com/android/support/room/parser/SqlParser.kt
index 6c2f1c6..5ed3ac8 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/parser/SqlParser.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/parser/SqlParser.kt
@@ -16,6 +16,7 @@
 
 package com.android.support.room.parser
 
+import com.android.support.room.ColumnInfo
 import org.antlr.v4.runtime.ANTLRInputStream
 import org.antlr.v4.runtime.BaseErrorListener
 import org.antlr.v4.runtime.CommonTokenStream
@@ -24,6 +25,9 @@
 import org.antlr.v4.runtime.tree.ParseTree
 import org.antlr.v4.runtime.tree.TerminalNode
 import java.util.ArrayList
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
 
 class QueryVisitor(val original: String, val syntaxErrors: ArrayList<String>,
                    statement: ParseTree) : SQLiteBaseVisitor<Void?>() {
@@ -148,5 +152,34 @@
     TEXT,
     INTEGER,
     REAL,
-    BLOB
+    BLOB;
+    fun getTypeMirrors(env : ProcessingEnvironment) : List<TypeMirror>? {
+        val typeUtils = env.typeUtils
+        return when(this) {
+            TEXT -> listOf(env.elementUtils.getTypeElement("java.lang.String").asType())
+            INTEGER -> listOf(typeUtils.getPrimitiveType(TypeKind.INT),
+                    typeUtils.getPrimitiveType(TypeKind.BYTE),
+                    typeUtils.getPrimitiveType(TypeKind.CHAR),
+                    typeUtils.getPrimitiveType(TypeKind.BOOLEAN),
+                    typeUtils.getPrimitiveType(TypeKind.LONG),
+                    typeUtils.getPrimitiveType(TypeKind.SHORT))
+            REAL -> listOf(typeUtils.getPrimitiveType(TypeKind.DOUBLE),
+                    typeUtils.getPrimitiveType(TypeKind.FLOAT))
+            BLOB -> listOf(typeUtils.getArrayType(
+                    typeUtils.getPrimitiveType(TypeKind.BYTE)))
+            else -> emptyList()
+        }
+    }
+    companion object {
+        // converts from ColumnInfo#SQLiteTypeAffinity
+        fun fromAnnotationValue(value : Int) : SQLTypeAffinity? {
+            return when(value) {
+                ColumnInfo.BLOB -> BLOB
+                ColumnInfo.INTEGER -> INTEGER
+                ColumnInfo.REAL -> REAL
+                ColumnInfo.TEXT -> TEXT
+                else -> null
+            }
+        }
+    }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt
index 77c23af..1b0fa43 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt
@@ -43,8 +43,9 @@
 
     fun fork(element: Element) : Context {
         val suppressedWarnings = SuppressWarningProcessor.getSuppressedWarnings(element)
+        val converters = CustomConverterProcessor.findConverters(this, element)
         return Context(processingEnv,
                 RLog(processingEnv, logger.suppressedWarnings + suppressedWarnings, element),
-                this.typeConverters)
+                converters + this.typeConverters)
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/CustomConverterProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/CustomConverterProcessor.kt
new file mode 100644
index 0000000..5892dd3
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/CustomConverterProcessor.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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.TypeConverter
+import com.android.support.room.TypeConverters
+import com.android.support.room.ext.hasAnnotation
+import com.android.support.room.ext.hasAnyOf
+import com.android.support.room.ext.toListOfClassTypes
+import com.android.support.room.ext.typeName
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_BAD_RETURN_TYPE
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_RECEIVE_1_PARAM
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import com.android.support.room.solver.types.CustomTypeConverterWrapper
+import com.android.support.room.vo.CustomTypeConverter
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes classes that are referenced in TypeConverters annotations.
+ */
+class CustomConverterProcessor(val context: Context, val element: TypeElement) {
+    companion object {
+        private val INVALID_RETURN_TYPES = setOf(TypeKind.ERROR, TypeKind.VOID, TypeKind.NONE)
+        fun findConverters(context: Context, element: Element): List<CustomTypeConverterWrapper> {
+            val annotation = MoreElements.getAnnotationMirror(element,
+                    TypeConverters::class.java).orNull()
+            return annotation?.let {
+                val converters = AnnotationMirrors.getAnnotationValue(annotation, "value")
+                        ?.toListOfClassTypes()
+                        ?.filter {
+                            MoreTypes.isType(it)
+                        }
+                        ?.flatMap {
+                            CustomConverterProcessor(context, MoreTypes.asTypeElement(it))
+                                    .process()
+                        }
+                converters?.let {
+                    reportDuplicates(context, converters)
+                }
+                converters?.map(::CustomTypeConverterWrapper)
+            } ?: emptyList<CustomTypeConverterWrapper>()
+        }
+
+        fun reportDuplicates(context: Context, converters : List<CustomTypeConverter>) {
+            val groupedByFrom = converters.groupBy { it.from.typeName() }
+            groupedByFrom.forEach {
+                it.value.groupBy { it.to.typeName() }.forEach {
+                    if (it.value.size > 1) {
+                        it.value.forEach { converter ->
+                            context.logger.e(converter.method, ProcessorErrors
+                                    .duplicateTypeConverters(it.value.minus(converter)))
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun process(): List<CustomTypeConverter> {
+        // using element utils instead of MoreElements to include statics.
+        val methods = ElementFilter
+                .methodsIn(context.processingEnv.elementUtils.getAllMembers(element))
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val converterMethods = methods.filter {
+            it.hasAnnotation(TypeConverter::class)
+        }
+        context.checker.check(converterMethods.isNotEmpty(), element, TYPE_CONVERTER_EMPTY_CLASS)
+        val allStatic = converterMethods.all { it.modifiers.contains(Modifier.STATIC) }
+        val constructors = ElementFilter.constructorsIn(
+                context.processingEnv.elementUtils.getAllMembers(element))
+        context.checker.check(allStatic || constructors.isEmpty() || constructors.any {
+            it.parameters.isEmpty()
+        }, element, TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+        return converterMethods.map {
+            processMethod(declaredType, it)
+        }.filterNotNull()
+    }
+
+    private fun processMethod(container: DeclaredType, methodElement: ExecutableElement)
+            : CustomTypeConverter? {
+        val asMember = context.processingEnv.typeUtils.asMemberOf(container, methodElement)
+        val executableType = MoreTypes.asExecutable(asMember)
+        val returnType = executableType.returnType
+        val invalidReturnType = INVALID_RETURN_TYPES.contains(returnType.kind)
+        context.checker.check(methodElement.hasAnyOf(Modifier.PUBLIC), methodElement,
+                TYPE_CONVERTER_MUST_BE_PUBLIC)
+        if (invalidReturnType) {
+            context.logger.e(methodElement, TYPE_CONVERTER_BAD_RETURN_TYPE)
+            return null
+        }
+        val returnTypeName = returnType.typeName()
+        context.checker.notUnbound(returnTypeName, methodElement,
+                TYPE_CONVERTER_UNBOUND_GENERIC)
+        val params = methodElement.parameters
+        if (params.size != 1) {
+            context.logger.e(methodElement, TYPE_CONVERTER_MUST_RECEIVE_1_PARAM)
+            return null
+        }
+        val param = params.map {
+            MoreTypes.asMemberOf(context.processingEnv.typeUtils, container, it)
+        }.first()
+        context.checker.notUnbound(param.typeName(), params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
+        return CustomTypeConverter(container, methodElement, param, returnType)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
index 84c4057..9e68b38 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
@@ -20,6 +20,7 @@
 import com.android.support.room.ext.RoomTypeNames
 import com.android.support.room.ext.hasAnnotation
 import com.android.support.room.ext.hasAnyOf
+import com.android.support.room.ext.toListOfClassTypes
 import com.android.support.room.verifier.DatabaseVerifier
 import com.android.support.room.vo.DaoMethod
 import com.android.support.room.vo.Database
@@ -29,12 +30,10 @@
 import com.google.auto.common.MoreTypes
 import com.squareup.javapoet.TypeName
 import javax.lang.model.element.AnnotationMirror
-import javax.lang.model.element.AnnotationValue
 import javax.lang.model.element.ElementKind
 import javax.lang.model.element.Modifier
 import javax.lang.model.element.TypeElement
 import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.SimpleAnnotationValueVisitor6
 
 
 class DatabaseProcessor(baseContext: Context, val element: TypeElement) {
@@ -128,41 +127,11 @@
         }
 
         val entityList = AnnotationMirrors.getAnnotationValue(dbAnnotation, "entities")
-        val listOfTypes = TO_LIST_OF_TYPES.visit(entityList, "entities")
+        val listOfTypes = entityList.toListOfClassTypes()
         context.checker.check(listOfTypes.isNotEmpty(), element,
                 ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES)
         return listOfTypes.map {
             EntityProcessor(context, MoreTypes.asTypeElement(it)).process()
         }
     }
-
-    // code below taken from dagger2
-    // compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
-    private val TO_LIST_OF_TYPES = object
-        : SimpleAnnotationValueVisitor6<List<TypeMirror>, String>() {
-
-        override fun visitArray(values: List<AnnotationValue>, elementName: String)
-                : List<TypeMirror> {
-            return values.map {
-                val tmp = TO_TYPE.visit(it)
-                tmp
-            }
-        }
-
-        override fun defaultAction(o: Any?, elementName: String?): List<TypeMirror> {
-            throw IllegalArgumentException(elementName + " is not an array: " + o)
-        }
-    }
-
-    private val TO_TYPE = object : SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
-
-        override fun visitType(t: TypeMirror, p: Void?): TypeMirror {
-            return t
-        }
-
-        override fun defaultAction(o: Any?, p: Void?): TypeMirror {
-            throw TypeNotPresentException(o!!.toString(), null)
-        }
-    }
-
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
index 1488305..9898609 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
@@ -27,7 +27,7 @@
     fun process(): Entity {
         context.checker.hasAnnotation(element, com.android.support.room.Entity::class,
                 ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
-        val pojo = PojoProcessor(context, element).process()
+        val pojo = PojoProcessor(context, element, FieldProcessor.BindingScope.TWO_WAY).process()
         val annotation = MoreElements.getAnnotationMirror(element,
                 com.android.support.room.Entity::class.java).orNull()
         val tableName : String
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
index c2583a3..9a83ff9 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
@@ -16,8 +16,9 @@
 
 package com.android.support.room.processor
 
-import com.android.support.room.ColumnName
+import com.android.support.room.ColumnInfo
 import com.android.support.room.PrimaryKey
+import com.android.support.room.parser.SQLTypeAffinity
 import com.android.support.room.vo.Field
 import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
@@ -25,29 +26,81 @@
 import javax.lang.model.element.Element
 import javax.lang.model.type.DeclaredType
 
-class FieldProcessor(baseContext: Context, val containing : DeclaredType, val element : Element) {
+class FieldProcessor(baseContext: Context, val containing: DeclaredType, val element: Element,
+                     val bindingScope: BindingScope) {
     val context = baseContext.fork(element)
-    fun process() : Field {
+    fun process(): Field {
         val member = context.processingEnv.typeUtils.asMemberOf(containing, element)
         val type = TypeName.get(member)
-        val columnNameAnnotation = MoreElements.getAnnotationMirror(element,
-                ColumnName::class.java)
+        val columnInfoAnnotation = MoreElements.getAnnotationMirror(element,
+                ColumnInfo::class.java)
         val name = element.simpleName.toString()
-        val columnName : String
-        if (columnNameAnnotation.isPresent) {
-            columnName = AnnotationMirrors
-                    .getAnnotationValue(columnNameAnnotation.get(), "value").value.toString()
+        val columnName: String
+        val affinity : SQLTypeAffinity?
+        if (columnInfoAnnotation.isPresent) {
+            val nameInAnnotation = AnnotationMirrors
+                    .getAnnotationValue(columnInfoAnnotation.get(), "name").value.toString()
+            columnName = if (nameInAnnotation == ColumnInfo.INHERIT_FIELD_NAME) {
+                name
+            } else {
+                nameInAnnotation
+            }
+
+            affinity = try {
+                val userDefinedAffinity = AnnotationMirrors
+                        .getAnnotationValue(columnInfoAnnotation.get(), "affinity").value.toString()
+                        .toInt()
+                SQLTypeAffinity.fromAnnotationValue(userDefinedAffinity)
+            } catch (ex : NumberFormatException) {
+                null
+            }
         } else {
             columnName = name
+            affinity = null
         }
         context.checker.notBlank(columnName, element,
                 ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
         context.checker.notUnbound(type, element,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
-        return Field(name = name,
+        val field = Field(name = name,
                 type = member,
                 primaryKey = MoreElements.isAnnotationPresent(element, PrimaryKey::class.java),
                 element = element,
-                columnName = columnName)
+                columnName = columnName,
+                affinity = affinity)
+
+        when (bindingScope) {
+            BindingScope.TWO_WAY -> {
+                val adapter = context.typeAdapterStore.findColumnTypeAdapter(field.type,
+                        field.affinity)
+                field.statementBinder = adapter
+                field.cursorValueReader = adapter
+                field.affinity = adapter?.typeAffinity ?: field.affinity
+                context.checker.check(adapter != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+            }
+            BindingScope.BIND_TO_STMT -> {
+                field.statementBinder = context.typeAdapterStore
+                        .findStatementValueBinder(field.type, field.affinity)
+                context.checker.check(field.statementBinder != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_STMT_BINDER)
+            }
+            BindingScope.READ_FROM_CURSOR -> {
+                field.cursorValueReader = context.typeAdapterStore
+                        .findCursorValueReader(field.type, field.affinity)
+                context.checker.check(field.cursorValueReader != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_CURSOR_READER)
+            }
+        }
+        return field
+    }
+
+    /**
+     * Defines what we need to assign
+     */
+    enum class BindingScope {
+        TWO_WAY, // both bind and read.
+        BIND_TO_STMT, // just value to statement
+        READ_FROM_CURSOR // just cursor to value
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt
index 1f819ae..d454351 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt
@@ -43,7 +43,8 @@
 /**
  * Processes any class as if it is a Pojo.
  */
-class PojoProcessor(baseContext : Context, val element: TypeElement) {
+class PojoProcessor(baseContext : Context, val element: TypeElement,
+                    val bindingScope : FieldProcessor.BindingScope) {
     val context = baseContext.fork(element)
     fun process(): Pojo {
         val declaredType = MoreTypes.asDeclared(element.asType())
@@ -57,7 +58,8 @@
                 .map { FieldProcessor(
                         baseContext = context,
                         containing = declaredType,
-                        element = it).process() }
+                        element = it,
+                        bindingScope = bindingScope).process() }
 
         val methods = allMembers
                 .filter {
@@ -77,7 +79,6 @@
 
         assignGetters(fields, getterCandidates)
         assignSetters(fields, setterCandidates)
-
         val pojo = Pojo(element, declaredType, fields)
         return pojo
     }
@@ -94,17 +95,13 @@
                         field.getter = FieldGetter(
                                 name = field.name,
                                 type = field.type,
-                                callType = CallType.FIELD,
-                                columnAdapter = context.typeAdapterStore
-                                        .findColumnTypeAdapter(field.type))
+                                callType = CallType.FIELD)
                     },
                     assignFromMethod = { match ->
                         field.getter = FieldGetter(
                                 name = match.simpleName.toString(),
                                 type = match.returnType,
-                                callType = CallType.METHOD,
-                                columnAdapter = context.typeAdapterStore
-                                        .findColumnTypeAdapter(match.returnType))
+                                callType = CallType.METHOD)
                     },
                     reportAmbiguity = { matching ->
                         context.logger.e(field.element,
@@ -126,18 +123,14 @@
                         field.setter = FieldSetter(
                                 name = field.name,
                                 type = field.type,
-                                callType = CallType.FIELD,
-                                columnAdapter = context.typeAdapterStore
-                                        .findColumnTypeAdapter(field.type))
+                                callType = CallType.FIELD)
                     },
                     assignFromMethod = { match ->
                         val paramType = match.parameters.first().asType()
                         field.setter = FieldSetter(
                                 name = match.simpleName.toString(),
                                 type = paramType,
-                                callType = CallType.METHOD,
-                                columnAdapter = context.typeAdapterStore
-                                        .findColumnTypeAdapter(paramType))
+                                callType = CallType.METHOD)
                     },
                     reportAmbiguity = { matching ->
                         context.logger.e(field.element,
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 6c3905a..4459285 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
@@ -20,8 +20,10 @@
 import com.android.support.room.Insert
 import com.android.support.room.Query
 import com.android.support.room.ext.RoomTypeNames
+import com.android.support.room.vo.CustomTypeConverter
 import com.android.support.room.vo.Field
 import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
 
 object ProcessorErrors {
     val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
@@ -56,13 +58,14 @@
     val DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES = "@Database annotation must specify list" +
             " of entities"
     val COLUMN_NAME_CANNOT_BE_EMPTY = "Column name cannot be blank. If you don't want to set it" +
-            ", just remove the @ColumnName annotation."
+            ", just remove the @ColumnInfo annotation or use @ColumnInfo.INHERIT_FIELD_NAME."
 
     val ENTITY_TABLE_NAME_CANNOT_BE_EMPTY = "Entity table name cannot be blank. If you don't want" +
             " to set it, just remove the tableName property."
 
-    val CANNOT_CONVERT_QUERY_PARAMETER_TO_STRING = "Query method parameters should either be a" +
-            " type that can be converted into String or a List / Array that contains such type."
+    val CANNOT_BIND_QUERY_PARAMETER_INTO_STMT = "Query method parameters should either be a" +
+            " type that can be converted into a database column or a List / Array that contains" +
+            " such type. You can consider adding a Type Adapter for this."
 
     val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE = "Query/Insert method parameters cannot " +
             "start with underscore (_)."
@@ -107,6 +110,13 @@
         return TOO_MANY_MATCHING_SETTERS.format(field, methodNames.joinToString(", "))
     }
 
+    val CANNOT_FIND_COLUMN_TYPE_ADAPTER = "Cannot figure out how to save this field into" +
+            " database. You can consider adding a type converter for it."
+
+    val CANNOT_FIND_STMT_BINDER = "Cannot figure out how to bind this field into a statement."
+
+    val CANNOT_FIND_CURSOR_READER = "Cannot figure out how to read this field from a cursor."
+
     private val MISSING_PARAMETER_FOR_BIND = "Each bind variable in the query must have a" +
             " matching method parameter. Cannot find method parameters for %s."
 
@@ -133,8 +143,8 @@
 
     fun duplicateDao(dao: TypeName, methodNames: List<String>): String {
         return """
-                All of these functions (${methodNames.joinToString(", ")}) return the same DAO
-                class ($dao).
+                All of these functions [${methodNames.joinToString(", ")}] return the same DAO
+                class [$dao].
                 A database can use a DAO only once so you should remove ${methodNames.size - 1} of
                 these conflicting DAO methods. If you are implementing any of these to fulfill an
                 interface, don't make it abstract, instead, implement the code that calls the
@@ -146,8 +156,8 @@
                            unusedColumns: List<String>, allColumns: List<String>,
                            unusedFields: List<Field>, allFields: List<Field>): String {
         val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) { """
-                The query returns some columns (${unusedColumns.joinToString(", ")}) which are not
-                use by $pojoTypeName. You can use @ColumnName annotation on the fields to specify
+                The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
+                use by $pojoTypeName. You can use @ColumnInfo annotation on the fields to specify
                 the mapping.
             """.trimIndent().replace(System.lineSeparator(), " ")
         } else {
@@ -155,7 +165,7 @@
         }
         val unusedFieldsWarning = if (unusedFields.isNotEmpty()) { """
                 $pojoTypeName has some fields
-                (${unusedFields.joinToString(", ") { it.columnName }}) which are not returned by the
+                [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by the
                 query. If they are not supposed to be read from the result, you can mark them with
                 @Ignore annotation.
             """.trimIndent().replace(System.lineSeparator(), " ")
@@ -172,4 +182,17 @@
             """.trimIndent().replace(System.lineSeparator(), " ")
     }
 
+    val TYPE_CONVERTER_UNBOUND_GENERIC = "Cannot use unbound generics in Type Converters."
+    val TYPE_CONVERTER_BAD_RETURN_TYPE = "Invalid return type for a type converter."
+    val TYPE_CONVERTER_MUST_RECEIVE_1_PARAM = "Type converters must receive 1 parameter."
+    val TYPE_CONVERTER_EMPTY_CLASS = "Class is referenced as a converter but it does not have any" +
+            " converter methods."
+    val TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR = "Classes that are used as TypeConverters must" +
+            " have no-argument public constructors."
+    val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
+
+    fun duplicateTypeConverters(converters : List<CustomTypeConverter>) : String {
+        return "Multiple methods define the same conversion. Conflicts with these:" +
+                " ${converters.joinToString(", ") { it.toString() }}"
+    }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParameterProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParameterProcessor.kt
index db07462..5ef5740 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParameterProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParameterProcessor.kt
@@ -29,7 +29,7 @@
         val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
         val parameterAdapter = context.typeAdapterStore.findQueryParameterAdapter(asMember)
         context.checker.check(parameterAdapter != null, element,
-                ProcessorErrors.CANNOT_CONVERT_QUERY_PARAMETER_TO_STRING)
+                ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
 
         val name = element.simpleName.toString()
         context.checker.check(!name.startsWith("_"), element,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt
index 1549804..fe39e6d 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt
@@ -20,7 +20,10 @@
 import com.android.support.room.ext.LifecyclesTypeNames
 import com.android.support.room.ext.hasAnnotation
 import com.android.support.room.parser.ParsedQuery
+import com.android.support.room.parser.SQLTypeAffinity
 import com.android.support.room.processor.Context
+import com.android.support.room.processor.EntityProcessor
+import com.android.support.room.processor.FieldProcessor
 import com.android.support.room.processor.PojoProcessor
 import com.android.support.room.solver.query.parameter.ArrayQueryParameterAdapter
 import com.android.support.room.solver.query.parameter.BasicQueryParameterAdapter
@@ -44,6 +47,7 @@
 import com.android.support.room.solver.types.ColumnTypeAdapter
 import com.android.support.room.solver.types.CompositeAdapter
 import com.android.support.room.solver.types.CompositeTypeConverter
+import com.android.support.room.solver.types.CursorValueReader
 import com.android.support.room.solver.types.NoOpConverter
 import com.android.support.room.solver.types.PrimitiveBooleanToIntConverter
 import com.android.support.room.solver.types.PrimitiveColumnTypeAdapter
@@ -66,9 +70,14 @@
  * Holds all type adapters and can create on demand composite type adapters to convert a type into a
  * database column.
  */
-class TypeAdapterStore(val context: Context,
-                       @VisibleForTesting vararg extras: Any) {
+class TypeAdapterStore(val context: Context, @VisibleForTesting vararg extras: Any) {
+    /**
+     * first type adapter has the highest priority
+     */
     private val columnTypeAdapters: List<ColumnTypeAdapter>
+    /**
+     * first converter has the highest priority
+     */
     private val typeConverters: List<TypeConverter>
 
     init {
@@ -121,16 +130,46 @@
     /**
      * Searches 1 way to bind a value into a statement.
      */
-    fun findStatementValueBinder(input : TypeMirror) : StatementValueBinder? {
-        val adapters = getAllColumnAdapters(input)
-        if (adapters.isNotEmpty()) {
-            return adapters.first()
+    fun findStatementValueBinder(input : TypeMirror, affinity: SQLTypeAffinity?)
+            : StatementValueBinder? {
+        val adapter = findDirectAdapterFor(input, affinity)
+        if (adapter != null) {
+            return adapter
         }
-        val binder = findTypeConverter(input, knownColumnTypeMirrors) ?: return null
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val binder = findTypeConverter(input, targetTypes) ?: return null
         return CompositeAdapter(input, getAllColumnAdapters(binder.to).first(), binder, null)
     }
 
     /**
+     * Returns which entities targets the given affinity.
+     */
+    private fun targetTypeMirrorsFor(affinity: SQLTypeAffinity?) : List<TypeMirror> {
+        val specifiedTargets = affinity?.getTypeMirrors(context.processingEnv)
+        return if(specifiedTargets == null || specifiedTargets.isEmpty()) {
+            knownColumnTypeMirrors
+        } else {
+            specifiedTargets
+        }
+    }
+
+    /**
+     * Searches 1 way to read it from cursor
+     */
+    fun findCursorValueReader(output: TypeMirror, affinity: SQLTypeAffinity?) : CursorValueReader? {
+        val adapter = findColumnTypeAdapter(output, affinity)
+        if (adapter != null) {
+            // two way is better
+            return adapter
+        }
+        // we could not find a two way version, search for anything
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val converter = findTypeConverter(targetTypes, output) ?: return null
+        return CompositeAdapter(output,
+                getAllColumnAdapters(converter.from).first(), null, converter)
+    }
+
+    /**
      * Tries to reverse the converter going through the same nodes, if possible.
      */
     @VisibleForTesting
@@ -156,13 +195,14 @@
      * Finds a two way converter, if you need 1 way, use findStatementValueBinder or
      * findCursorValueReader.
      */
-    fun findColumnTypeAdapter(out: TypeMirror): ColumnTypeAdapter? {
-        val adapters = getAllColumnAdapters(out)
-        if (adapters.isNotEmpty()) {
-            return adapters.first()
+    fun findColumnTypeAdapter(out: TypeMirror, affinity: SQLTypeAffinity?)
+            : ColumnTypeAdapter? {
+        val adapter = findDirectAdapterFor(out, affinity)
+        if (adapter != null) {
+            return adapter
         }
-        val intoStatement = findTypeConverter(out, knownColumnTypeMirrors)
-                ?: return null
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val intoStatement = findTypeConverter(out, targetTypes) ?: return null
         // ok found a converter, try the reverse now
         val fromCursor = reverse(intoStatement) ?: findTypeConverter(intoStatement.to, out)
                 ?: return null
@@ -170,6 +210,14 @@
                 fromCursor)
     }
 
+    private fun findDirectAdapterFor(out: TypeMirror, affinity: SQLTypeAffinity?)
+            : ColumnTypeAdapter? {
+        val adapter = getAllColumnAdapters(out).firstOrNull {
+            affinity == null || it.typeAffinity == affinity
+        }
+        return adapter
+    }
+
     fun findTypeConverter(input: TypeMirror, output: TypeMirror): TypeConverter? {
         return findTypeConverter(listOf(input), listOf(output))
     }
@@ -241,12 +289,16 @@
             }
             val asElement = MoreTypes.asElement(typeMirror)
             if (asElement.hasAnnotation(Entity::class)) {
-                return EntityRowAdapter(typeMirror)
+                // TODO we might parse this too much, would be nice to scope these parsers
+                // at least for entities.
+                return EntityRowAdapter(EntityProcessor(context,
+                        MoreElements.asType(asElement)).process())
             }
             // if result is unknown, we are fine w/ single column result
             val resultInfo = query.resultInfo
             if ((resultInfo?.columns?.size ?: 1) == 1) {
-                val singleColumn = findColumnTypeAdapter(typeMirror)
+                val singleColumn = findColumnTypeAdapter(typeMirror,
+                        resultInfo?.columns?.get(0)?.type)
                 if (singleColumn != null) {
                     return SingleColumnRowAdapter(singleColumn)
                 }
@@ -255,7 +307,8 @@
             if (resultInfo != null && resultInfo.error == null) {
                 val pojo = PojoProcessor(
                         baseContext = context,
-                        element = MoreTypes.asTypeElement(typeMirror)
+                        element = MoreTypes.asTypeElement(typeMirror),
+                        bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR
                 ).process()
                 return PojoRowAdapter(
                         context = context,
@@ -265,7 +318,7 @@
             }
             return null
         } else {
-            val singleColumn = findColumnTypeAdapter(typeMirror) ?: return null
+            val singleColumn = findColumnTypeAdapter(typeMirror, null) ?: return null
             return SingleColumnRowAdapter(singleColumn)
         }
     }
@@ -275,14 +328,15 @@
                 && (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)
                 || MoreTypes.isTypeOf(java.util.Set::class.java, typeMirror))) {
             val declared = MoreTypes.asDeclared(typeMirror)
-            val binder = findStatementValueBinder(declared.typeArguments.first()) ?: return null
+            val binder = findStatementValueBinder(declared.typeArguments.first(),
+                    null) ?: return null
             return CollectionQueryParameterAdapter(binder)
         } else if (typeMirror is ArrayType) {
             val component = typeMirror.componentType
-            val binder = findStatementValueBinder(component) ?: return null
+            val binder = findStatementValueBinder(component, null) ?: return null
             return ArrayQueryParameterAdapter(binder)
         } else {
-            val binder = findStatementValueBinder(typeMirror) ?: return null
+            val binder = findStatementValueBinder(typeMirror, null) ?: return null
             return BasicQueryParameterAdapter(binder)
         }
     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/ArrayQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
index 3446c78..10707a3 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
@@ -20,7 +20,6 @@
 import com.android.support.room.ext.T
 import com.android.support.room.ext.typeName
 import com.android.support.room.solver.CodeGenScope
-import com.android.support.room.solver.types.ColumnTypeAdapter
 import com.android.support.room.solver.types.StatementValueBinder
 import com.squareup.javapoet.TypeName
 
@@ -33,8 +32,8 @@
                             scope: CodeGenScope) {
         scope.builder().apply {
             val itrVar = scope.getTmpVar("_item")
-            beginControlFlow("for ($T $L : $L)", bindAdapter.out().typeName(), itrVar, inputVarName)
-                    .apply {
+            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
+                    inputVarName).apply {
                         bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
                         addStatement("$L ++", startIndexVarName)
             }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/BasicQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/BasicQueryParameterAdapter.kt
index bffa755..b025f81 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/BasicQueryParameterAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/BasicQueryParameterAdapter.kt
@@ -17,7 +17,6 @@
 package com.android.support.room.solver.query.parameter
 
 import com.android.support.room.solver.CodeGenScope
-import com.android.support.room.solver.types.ColumnTypeAdapter
 import com.android.support.room.solver.types.StatementValueBinder
 
 /**
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/CollectionQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
index 8fccdbc..45f1088 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
@@ -32,8 +32,8 @@
                             scope: CodeGenScope) {
         scope.builder().apply {
             val itrVar = scope.getTmpVar("_item")
-            beginControlFlow("for ($T $L : $L)", bindAdapter.out().typeName(), itrVar, inputVarName)
-                    .apply {
+            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
+                    inputVarName).apply {
                         bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
                         addStatement("$L ++", startIndexVarName)
                     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/EntityRowAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/EntityRowAdapter.kt
index a561144..991ffca 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/EntityRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/EntityRowAdapter.kt
@@ -17,25 +17,18 @@
 package com.android.support.room.solver.query.result
 
 import com.android.support.room.ext.L
-import com.android.support.room.ext.RoomTypeNames
-import com.android.support.room.ext.T
-import com.android.support.room.ext.typeName
+import com.android.support.room.ext.N
 import com.android.support.room.solver.CodeGenScope
-import com.squareup.javapoet.ParameterizedTypeName
-import javax.lang.model.type.TypeMirror
+import com.android.support.room.vo.Entity
+import com.android.support.room.writer.EntityCursorConverterWriter
 
-class EntityRowAdapter(type : TypeMirror) : RowAdapter(type) {
+class EntityRowAdapter(val entity : Entity) : RowAdapter(entity.type) {
     override fun init(cursorVarName: String, scope : CodeGenScope) : RowConverter {
-        val converterVar = scope.getTmpVar("_converter")
-        scope.builder()
-                .addStatement("final $T $L = $T.getConverter($T.class)",
-                ParameterizedTypeName.get(RoomTypeNames.CURSOR_CONVERTER, out.typeName()),
-                converterVar, RoomTypeNames.ROOM, out.typeName())
+        val methodSpec = scope.writer.getOrCreateMethod(EntityCursorConverterWriter(entity))
         return object : RowConverter {
             override fun convert(outVarName: String, cursorVarName: String) {
                 scope.builder()
-                        .addStatement("$L = $L.convert($L)", outVarName, converterVar,
-                                cursorVarName)
+                        .addStatement("$L = $N($L)", outVarName, methodSpec, cursorVarName)
             }
         }
     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
index 9c79eb2..ade789d 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
@@ -84,16 +84,17 @@
                     mapping.associations.forEach { mapping ->
                         val field = mapping.field
                         val index = mapping.index
-                        val columnAdapter = field.getter.columnAdapter
+                        val cursorValueReader = field.cursorValueReader
                         when (field.setter.callType) {
                             CallType.FIELD -> {
-                                columnAdapter?.readFromCursor("$outVarName.${field.getter.name}",
+                                cursorValueReader
+                                        ?.readFromCursor("$outVarName.${field.getter.name}",
                                         cursorVarName, index.toString(), scope)
                             }
                             CallType.METHOD -> {
                                 val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
                                 addStatement("final $T $L", field.getter.type.typeName(), tmpField)
-                                columnAdapter?.readFromCursor(tmpField, cursorVarName,
+                                cursorValueReader?.readFromCursor(tmpField, cursorVarName,
                                         index.toString(), scope)
                                 addStatement("$L.$L($L)", outVarName, field.setter.name, tmpField)
                             }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/ColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/ColumnTypeAdapter.kt
index e7f4a35..998fc35 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/ColumnTypeAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/ColumnTypeAdapter.kt
@@ -17,7 +17,6 @@
 package com.android.support.room.solver.types
 
 import com.android.support.room.parser.SQLTypeAffinity
-import com.android.support.room.solver.CodeGenScope
 import com.squareup.javapoet.TypeName
 import javax.lang.model.type.TypeMirror
 
@@ -27,5 +26,5 @@
 abstract class ColumnTypeAdapter(val out: TypeMirror, val typeAffinity: SQLTypeAffinity) :
         StatementValueBinder, CursorValueReader {
     val outTypeName: TypeName by lazy { TypeName.get(out) }
-    override fun out() = out
+    override fun typeMirror() = out
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CursorValueReader.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CursorValueReader.kt
index ddc6b6d..76a325b 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CursorValueReader.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CursorValueReader.kt
@@ -24,7 +24,7 @@
  * see: StatementValueBinder
  */
 interface CursorValueReader {
-    fun out() : TypeMirror
+    fun typeMirror() : TypeMirror
     fun readFromCursor(outVarName : String, cursorVarName: String, indexVarName: String,
                                 scope: CodeGenScope)
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CustomTypeConverterWrapper.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CustomTypeConverterWrapper.kt
new file mode 100644
index 0000000..9de46cd
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/CustomTypeConverterWrapper.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.solver.types
+
+import com.android.support.room.ext.L
+import com.android.support.room.ext.N
+import com.android.support.room.ext.T
+import com.android.support.room.solver.CodeGenScope
+import com.android.support.room.vo.CustomTypeConverter
+import com.android.support.room.writer.ClassWriter
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Wraps a type converter specified by the developer and forwards calls to it.
+ */
+class CustomTypeConverterWrapper(val custom: CustomTypeConverter)
+    : TypeConverter(custom.from, custom.to) {
+
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            if (custom.isStatic) {
+                addStatement("$L = $T.$L($L)",
+                        outputVarName, custom.typeName,
+                        custom.methodName, inputVarName)
+            } else {
+                addStatement("$L = $N.$L($L)",
+                        outputVarName, typeConverter(scope),
+                        custom.methodName, inputVarName)
+            }
+        }
+    }
+
+    fun typeConverter(scope: CodeGenScope) : FieldSpec {
+        val baseName = (custom.typeName as ClassName).simpleName().decapitalize()
+        return scope.writer.getOrCreateField(object : ClassWriter.SharedFieldSpec(
+                baseName, custom.typeName) {
+            override fun getUniqueKey(): String {
+                return "converter_${custom.typeName}"
+            }
+
+            override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+                builder.addModifiers(Modifier.PRIVATE)
+                builder.addModifiers(Modifier.FINAL)
+                builder.initializer("new $T()", custom.typeName)
+            }
+        })
+    }
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/StatementValueBinder.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/StatementValueBinder.kt
index 5ef0ced..9ec8e87 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/StatementValueBinder.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/StatementValueBinder.kt
@@ -24,7 +24,7 @@
  * see: CursorValueReader
  */
 interface StatementValueBinder {
-    fun out() : TypeMirror
+    fun typeMirror() : TypeMirror
     fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
                             scope: CodeGenScope)
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/TypeConverter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/TypeConverter.kt
index a1ef986..e0c9aab 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/types/TypeConverter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/types/TypeConverter.kt
@@ -24,8 +24,5 @@
  * A code generator that can convert from 1 type to another
  */
 abstract class TypeConverter(val from: TypeMirror, val to: TypeMirror) {
-    val fromTypeName: TypeName by lazy { TypeName.get(from) }
-    val toTypeName: TypeName by lazy { TypeName.get(to) }
-    abstract fun convert(inputVarName: String, outputVarName: String,
-                                scope: CodeGenScope)
+    abstract fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope)
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/CustomTypeConverter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/CustomTypeConverter.kt
new file mode 100644
index 0000000..4385e95
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/CustomTypeConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.android.support.room.ext.hasAnyOf
+import com.android.support.room.ext.typeName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Generated when we parse a method annotated with TypeConverter.
+ */
+data class CustomTypeConverter(val type: TypeMirror,
+                               val method : ExecutableElement,
+                               val from: TypeMirror, val to: TypeMirror) {
+    val typeName: TypeName by lazy { type.typeName() }
+    val fromTypeName: TypeName by lazy { from.typeName() }
+    val toTypeName: TypeName by lazy { to.typeName() }
+    val methodName by lazy { method.simpleName.toString() }
+    val isStatic by lazy { method.hasAnyOf(Modifier.STATIC) }
+}
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
index 5fedec8..964419e 100644
--- 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
@@ -17,21 +17,11 @@
 package com.android.support.room.vo
 
 import com.android.support.room.ext.typeName
-import com.squareup.javapoet.ClassName
 import javax.lang.model.element.TypeElement
 import javax.lang.model.type.DeclaredType
 
 data class Entity(val element : TypeElement, val tableName : String, val type: DeclaredType,
                   val fields : List<Field>) {
-    val converterClassName by lazy {
-        val typeName = this.typeName
-        if (typeName is ClassName) {
-            "${typeName.simpleName()}_CursorConverter"
-        } else {
-            "${typeName}_CursorConverter"
-        }
-    }
-
     val typeName by lazy { type.typeName() }
 
     val primaryKeys by lazy {
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
index 750395e..ed79a98 100644
--- 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
@@ -18,14 +18,22 @@
 
 import com.android.support.room.ext.typeName
 import com.android.support.room.parser.SQLTypeAffinity
+import com.android.support.room.solver.types.ColumnTypeAdapter
+import com.android.support.room.solver.types.CursorValueReader
+import com.android.support.room.solver.types.StatementValueBinder
 import com.squareup.javapoet.TypeName
 import javax.lang.model.element.Element
 import javax.lang.model.type.TypeMirror
 
 data class Field(val element: Element, val name: String, val type: TypeMirror,
-                 val primaryKey: Boolean, val columnName: String = name) {
+                 val primaryKey: Boolean, var affinity : SQLTypeAffinity?,
+                 val columnName: String = name) {
     lateinit var getter: FieldGetter
     lateinit var setter: FieldSetter
+    // binds the field into a statement
+    var statementBinder: StatementValueBinder? = null
+    // reads this field from a cursor column
+    var cursorValueReader: CursorValueReader? = null
     val typeName: TypeName by lazy { type.typeName() }
     /**
      * List of names that include variations.
@@ -73,10 +81,6 @@
      * definition to be used in create query
      */
     val databaseDefinition by lazy {
-        val affinity = let {
-            val adapter = getter.columnAdapter ?: setter.columnAdapter
-            adapter?.typeAffinity ?: SQLTypeAffinity.TEXT
-        }
-        "`$columnName` ${affinity.name}"
+        "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}"
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
index 357ae24..4833b71 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
@@ -16,8 +16,6 @@
 
 package com.android.support.room.vo
 
-import com.android.support.room.solver.types.ColumnTypeAdapter
 import javax.lang.model.type.TypeMirror
 
-data class FieldGetter(val name : String, val type : TypeMirror, val callType: CallType,
-                       val columnAdapter : ColumnTypeAdapter?)
+data class FieldGetter(val name : String, val type : TypeMirror, val callType: CallType)
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
index 22fdeac..effe076 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
@@ -19,5 +19,4 @@
 import com.android.support.room.solver.types.ColumnTypeAdapter
 import javax.lang.model.type.TypeMirror
 
-data class FieldSetter(val name : String, val type : TypeMirror, val callType: CallType,
-                       val columnAdapter : ColumnTypeAdapter?)
+data class FieldSetter(val name : String, val type : TypeMirror, val callType: CallType)
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/ClassWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/ClassWriter.kt
index b8d86ab..8f27aa7 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/ClassWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/ClassWriter.kt
@@ -63,24 +63,25 @@
 
     fun getOrCreateField(sharedField: SharedFieldSpec): FieldSpec {
         return sharedFieldSpecs.getOrPut(sharedField.getUniqueKey(), {
-            sharedField.build(makeUnique(sharedFieldNames, sharedField.baseName))
+            sharedField.build(this, makeUnique(sharedFieldNames, sharedField.baseName))
         })
     }
 
     fun getOrCreateMethod(sharedMethod: SharedMethodSpec): MethodSpec {
         return sharedMethodSpecs.getOrPut(sharedMethod.getUniqueKey(), {
-            sharedMethod.build(makeUnique(sharedMethodNames, sharedMethod.baseName))
+            sharedMethod.build(this, makeUnique(sharedMethodNames, sharedMethod.baseName))
         })
     }
 
     abstract class SharedFieldSpec(val baseName: String, val type: TypeName) {
 
         abstract fun getUniqueKey(): String
-        abstract fun prepare(builder: FieldSpec.Builder)
 
-        fun build(name: String): FieldSpec {
+        abstract fun prepare(writer: ClassWriter, builder: FieldSpec.Builder)
+
+        fun build(classWriter: ClassWriter, name: String): FieldSpec {
             val builder = FieldSpec.builder(type, name)
-            prepare(builder)
+            prepare(classWriter, builder)
             return builder.build()
         }
     }
@@ -88,11 +89,11 @@
     abstract class SharedMethodSpec(val baseName: String) {
 
         abstract fun getUniqueKey(): String
-        abstract fun prepare(builder: MethodSpec.Builder)
+        abstract fun prepare(writer: ClassWriter, builder: MethodSpec.Builder)
 
-        fun build(name: String): MethodSpec {
+        fun build(writer: ClassWriter, name: String): MethodSpec {
             val builder = MethodSpec.methodBuilder(name)
-            prepare(builder)
+            prepare(writer, builder)
             return builder.build()
         }
     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/DaoWriter.kt
index 55ec4b9..4b7f46b 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/DaoWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/DaoWriter.kt
@@ -359,7 +359,7 @@
             return "${entity.typeName} $onConflictText"
         }
 
-        override fun prepare(builder: FieldSpec.Builder) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
             builder.addModifiers(FINAL, PRIVATE)
         }
     }
@@ -367,7 +367,7 @@
     class DeleteOrUpdateAdapterField(val entity: Entity) : SharedFieldSpec(
             "deletionAdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
             RoomTypeNames.DELETE_OR_UPDATE_ADAPTER) {
-        override fun prepare(builder: FieldSpec.Builder) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
             builder.addModifiers(PRIVATE, FINAL)
         }
 
@@ -378,7 +378,7 @@
 
     class PreparedStatementField(val method: QueryMethod) : SharedFieldSpec(
             "preparedStmtOf${method.name.capitalize()}", RoomTypeNames.SHARED_SQLITE_STMT) {
-        override fun prepare(builder: FieldSpec.Builder) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
             builder.addModifiers(PRIVATE, FINAL)
         }
 
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt
index 9b634b5..88955e6 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt
@@ -19,7 +19,6 @@
 import com.android.support.room.ext.AndroidTypeNames
 import com.android.support.room.ext.L
 import com.android.support.room.ext.N
-import com.android.support.room.ext.RoomTypeNames
 import com.android.support.room.ext.S
 import com.android.support.room.ext.T
 import com.android.support.room.ext.typeName
@@ -28,39 +27,34 @@
 import com.android.support.room.vo.CallType.METHOD
 import com.android.support.room.vo.Entity
 import com.android.support.room.vo.Field
-import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.CodeBlock
 import com.squareup.javapoet.MethodSpec
 import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier.PUBLIC
+import stripNonJava
+import javax.lang.model.element.Modifier.PRIVATE
 
-class EntityCursorConverterWriter(val entity: Entity) : ClassWriter(entity.typeName as ClassName) {
-    override fun createTypeSpecBuilder(): TypeSpec.Builder {
-        return TypeSpec.classBuilder(entity.converterClassName)
-                .apply {
-                    addSuperinterface(ParameterizedTypeName.get(RoomTypeNames.CURSOR_CONVERTER,
-                            entity.typeName))
-                    addModifiers(PUBLIC)
-                    addMethod(
-                            MethodSpec.methodBuilder("convert").apply {
-                                val cursorParam = ParameterSpec
-                                        .builder(AndroidTypeNames.CURSOR, "cursor").build()
-                                addParameter(cursorParam)
-                                addAnnotation(Override::class.java)
-                                addModifiers(PUBLIC)
-                                returns(entity.typeName)
-                                addCode(buildConvertMethodBody(cursorParam))
-                            }.build()
-                    )
-                }
+class EntityCursorConverterWriter(val entity: Entity) : ClassWriter.SharedMethodSpec(
+        "entityCursorConverter_${entity.typeName.toString().stripNonJava()}") {
+    override fun getUniqueKey(): String {
+        return "generic_entity_converter_of_${entity.element.qualifiedName}"
     }
 
-    private fun buildConvertMethodBody(cursorParam: ParameterSpec) : CodeBlock {
+    override fun prepare(writer: ClassWriter, builder: MethodSpec.Builder) {
+        builder.apply {
+            val cursorParam = ParameterSpec
+                    .builder(AndroidTypeNames.CURSOR, "cursor").build()
+            addParameter(cursorParam)
+            addModifiers(PRIVATE)
+            returns(entity.typeName)
+            addCode(buildConvertMethodBody(writer, cursorParam))
+        }
+    }
+
+    private fun buildConvertMethodBody(writer: ClassWriter, cursorParam: ParameterSpec)
+            : CodeBlock {
         // TODO support arg constructor
-        val scope = CodeGenScope(this)
+        val scope = CodeGenScope(writer)
         val entityVar = scope.getTmpVar("_entity")
         scope.builder().apply {
             addStatement("$T $L = new $T()", entity.typeName, entityVar, entity.typeName)
@@ -76,7 +70,7 @@
                             val fields = it.value
                             fields.forEach { field ->
                                 beginControlFlow("if ($S.equals($L))", field.name, colNameVar)
-                                    readField(field, cursorParam, colIndexVar, entityVar, scope)
+                                readField(field, cursorParam, colIndexVar, entityVar, scope)
                                 endControlFlow()
                             }
                         }
@@ -92,20 +86,19 @@
         return scope.builder().build()
     }
 
-    private fun readField(field : Field, cursorParam: ParameterSpec,
-                          indexVar : String, entityVar : String, scope: CodeGenScope) {
+    private fun readField(field: Field, cursorParam: ParameterSpec,
+                          indexVar: String, entityVar: String, scope: CodeGenScope) {
         scope.builder().apply {
-            val columnAdapter = field.getter.columnAdapter
+            val reader = field.cursorValueReader
             when (field.setter.callType) {
                 FIELD -> {
-                    columnAdapter
-                            ?.readFromCursor("$entityVar.${field.getter.name}", cursorParam.name,
+                    reader?.readFromCursor("$entityVar.${field.getter.name}", cursorParam.name,
                                     indexVar, scope)
                 }
                 METHOD -> {
                     val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
                     addStatement("final $T $L", field.getter.type.typeName(), tmpField)
-                    columnAdapter?.readFromCursor(tmpField, cursorParam.name,
+                    reader?.readFromCursor(tmpField, cursorParam.name,
                             indexVar, scope)
                     addStatement("$L.$L($L)", entityVar, field.setter.name, tmpField)
                 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt
index 5de0026..0cdba72 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt
@@ -59,13 +59,13 @@
                 returns(TypeName.VOID)
                 addModifiers(PUBLIC)
                 entity.primaryKeys.forEachIndexed { index, field ->
-                    field.getter.columnAdapter?.let { adapter ->
+                    field.statementBinder?.let { binder ->
                         val varName = if (field.getter.callType == CallType.FIELD) {
                             "$valueParam.${field.name}"
                         } else {
                             "$valueParam.${field.getter.name}()"
                         }
-                        adapter.bindToStmt(stmtParam, "${index + 1}", varName, bindScope)
+                        binder.bindToStmt(stmtParam, "${index + 1}", varName, bindScope)
                     }
                 }
                 addCode(bindScope.builder().build())
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt
index 03085cf..1b00b6c 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt
@@ -63,13 +63,13 @@
                 returns(TypeName.VOID)
                 addModifiers(PUBLIC)
                 entity.fields.forEachIndexed { index, field ->
-                    field.getter.columnAdapter?.let { adapter ->
+                    field.statementBinder?.let { binder ->
                         val varName = if (field.getter.callType == CallType.FIELD) {
                             "$valueParam.${field.name}"
                         } else {
                             "$valueParam.${field.getter.name}()"
                         }
-                        adapter.bindToStmt(stmtParam, "${index + 1}", varName, bindScope)
+                        binder.bindToStmt(stmtParam, "${index + 1}", varName, bindScope)
                     }
                 }
                 addCode(bindScope.builder().build())
diff --git a/room/compiler/src/test/data/common/input/User.java b/room/compiler/src/test/data/common/input/User.java
index e82bc4d..3b3d403 100644
--- a/room/compiler/src/test/data/common/input/User.java
+++ b/room/compiler/src/test/data/common/input/User.java
@@ -22,7 +22,7 @@
     int uid;
     String name;
     private String lastName;
-    @ColumnName("ageColumn")
+    @ColumnInfo(name = "ageColumn")
     public int age;
 
     public String getLastName() {
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index 3fac730..63e9757 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -3,9 +3,7 @@
 import android.database.Cursor;
 import com.android.support.lifecycle.ComputableLiveData;
 import com.android.support.lifecycle.LiveData;
-import com.android.support.room.CursorConverter;
 import com.android.support.room.InvalidationTracker.Observer;
-import com.android.support.room.Room;
 import com.android.support.room.RoomDatabase;
 import com.android.support.room.RoomSQLiteQuery;
 import com.android.support.room.util.StringUtil;
@@ -54,10 +52,9 @@
         _statement.bindLong(_argIndex, id);
         final Cursor _cursor = __db.query(_statement);
         try {
-            final CursorConverter<User> _converter = Room.getConverter(User.class);
             final User _result;
             if(_cursor.moveToFirst()) {
-                _result = _converter.convert(_cursor);
+                _result = __entityCursorConverter_fooBarUser(_cursor);
             } else {
                 _result = null;
             }
@@ -86,10 +83,9 @@
         }
         final Cursor _cursor = __db.query(_statement);
         try {
-            final CursorConverter<User> _converter = Room.getConverter(User.class);
             final User _result;
             if(_cursor.moveToFirst()) {
-                _result = _converter.convert(_cursor);
+                _result = __entityCursorConverter_fooBarUser(_cursor);
             } else {
                 _result = null;
             }
@@ -117,11 +113,10 @@
         }
         final Cursor _cursor = __db.query(_statement);
         try {
-            final CursorConverter<User> _converter = Room.getConverter(User.class);
             final List<User> _result = new ArrayList<User>(_cursor.getCount());
             while(_cursor.moveToNext()) {
                 final User _item_1;
-                _item_1 = _converter.convert(_cursor);
+                _item_1 = __entityCursorConverter_fooBarUser(_cursor);
                 _result.add(_item_1);
             }
             return _result;
@@ -244,10 +239,9 @@
                 }
                 final Cursor _cursor = __db.query(_statement);
                 try {
-                    final CursorConverter<User> _converter = Room.getConverter(User.class);
                     final User _result;
                     if(_cursor.moveToFirst()) {
-                        _result = _converter.convert(_cursor);
+                        _result = __entityCursorConverter_fooBarUser(_cursor);
                     } else {
                         _result = null;
                     }
@@ -295,11 +289,10 @@
                 }
                 final Cursor _cursor = __db.query(_statement);
                 try {
-                    final CursorConverter<User> _converter = Room.getConverter(User.class);
                     final List<User> _result = new ArrayList<User>(_cursor.getCount());
                     while(_cursor.moveToNext()) {
                         final User _item_1;
-                        _item_1 = _converter.convert(_cursor);
+                        _item_1 = __entityCursorConverter_fooBarUser(_cursor);
                         _result.add(_item_1);
                     }
                     return _result;
@@ -314,4 +307,37 @@
             }
         }.getLiveData();
     }
-}
\ No newline at end of file
+
+    private User __entityCursorConverter_fooBarUser(Cursor cursor) {
+        User _entity = new User();
+        int _columnIndex = 0;
+        for (String _columnName : cursor.getColumnNames()) {
+            switch(_columnName.hashCode()) {
+                case 115792: {
+                    if ("uid".equals(_columnName)) {
+                        _entity.uid = cursor.getInt(_columnIndex);
+                    }
+                }
+                case 3373707: {
+                    if ("name".equals(_columnName)) {
+                        _entity.name = cursor.getString(_columnIndex);
+                    }
+                }
+                case -1459599807: {
+                    if ("lastName".equals(_columnName)) {
+                        final String _tmpLastName;
+                        _tmpLastName = cursor.getString(_columnIndex);
+                        _entity.setLastName(_tmpLastName);
+                    }
+                }
+                case 1358970165: {
+                    if ("age".equals(_columnName)) {
+                        _entity.age = cursor.getInt(_columnIndex);
+                    }
+                }
+            }
+            _columnIndex ++;
+        }
+        return _entity;
+    }
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/CustomConverterProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/CustomConverterProcessorTest.kt
new file mode 100644
index 0000000..5868e0a
--- /dev/null
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/CustomConverterProcessorTest.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 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.TypeConverter
+import com.android.support.room.ext.typeName
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
+import com.android.support.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import com.android.support.room.testing.TestInvocation
+import com.android.support.room.vo.CustomTypeConverter
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import com.squareup.javapoet.TypeVariableName
+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 simpleRun
+import java.util.Date
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class CustomConverterProcessorTest {
+    companion object {
+        val CONVERTER = ClassName.get("foo.bar", "MyConverter")!!
+        val CONVERTER_QNAME = CONVERTER.packageName() + "." + CONVERTER.simpleName()
+        val CONTAINER = JavaFileObjects.forSourceString("foo.bar.Container",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                @TypeConverters(foo.bar.MyConverter.class)
+                public class Container {}
+                """)
+    }
+
+    @Test
+    fun validCase() {
+        singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box()))
+        { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveFrom() {
+        singleClass(createConverter(TypeName.SHORT, TypeName.CHAR.box())) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveTo() {
+        singleClass(createConverter(TypeName.INT.box(), TypeName.DOUBLE)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.INT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveBoth() {
+        singleClass(createConverter(TypeName.INT, TypeName.DOUBLE)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.INT))
+            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun nonNullButNotBoxed() {
+        val string = String::class.typeName()
+        val date = Date::class.typeName()
+        singleClass(createConverter(string, date)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(string as TypeName))
+            assertThat(converter?.toTypeName, `is`(date as TypeName))
+        }
+    }
+
+    @Test
+    fun parametrizedTypeUnbound() {
+        val typeVarT = TypeVariableName.get("T")
+        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
+        val typeVarK = TypeVariableName.get("K")
+        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
+        singleClass(createConverter(list, map, listOf(typeVarK, typeVarT))) {
+            converter, invocation ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_UNBOUND_GENERIC)
+    }
+
+    @Test
+    fun parametrizedTypeSpecific() {
+        val string = String::class.typeName()
+        val date = Date::class.typeName()
+        val list = ParameterizedTypeName.get(List::class.typeName(), string)
+        val map = ParameterizedTypeName.get(Map::class.typeName(), string, date)
+        singleClass(createConverter(list, map)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(list as TypeName))
+            assertThat(converter?.toTypeName, `is`(map as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testNoConverters() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                public class ${CONVERTER.simpleName()} {
+                }
+                """)) { converter, invocation ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
+    }
+
+    @Test
+    fun checkNoArgConstructor() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import com.android.support.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    public ${CONVERTER.simpleName()}(int x) {}
+                    @TypeConverter
+                    public int x(short y) {return 0;}
+                }
+                """)) { converter, invocation ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+    }
+
+    @Test
+    fun checkNoArgConstructor_withStatic() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import com.android.support.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    public ${CONVERTER.simpleName()}(int x) {}
+                    @TypeConverter
+                    public static int x(short y) {return 0;}
+                }
+                """)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.INT))
+            assertThat(converter?.isStatic, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun checkPublic() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import com.android.support.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    @TypeConverter static int x(short y) {return 0;}
+                    @TypeConverter private static int y(boolean y) {return 0;}
+                }
+                """)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.INT))
+            assertThat(converter?.isStatic, `is`(true))
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MUST_BE_PUBLIC).and()
+                .withErrorCount(2)
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    @Test
+    fun parametrizedTypeBoundViaParent() {
+        val typeVarT = TypeVariableName.get("T")
+        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
+        val typeVarK = TypeVariableName.get("K")
+        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
+
+        val baseConverter = createConverter(list, map, listOf(typeVarT, typeVarK))
+        val extendingQName = "foo.bar.Extending"
+        val extendingClass = JavaFileObjects.forSourceString(extendingQName,
+                "package foo.bar;\n" +
+                        TypeSpec.classBuilder(ClassName.bestGuess(extendingQName)).apply {
+                            superclass(
+                                    ParameterizedTypeName.get(CONVERTER, String::class.typeName(),
+                                    Integer::class.typeName()))
+                        }.build().toString())
+
+        simpleRun(baseConverter, extendingClass) { invocation ->
+            val element = invocation.processingEnv.elementUtils.getTypeElement(extendingQName)
+            val converter = CustomConverterProcessor(invocation.context, element)
+                    .process().firstOrNull()
+            assertThat(converter?.fromTypeName, `is`(ParameterizedTypeName.get(
+                    List::class.typeName(), String::class.typeName()) as TypeName
+            ))
+            assertThat(converter?.toTypeName, `is`(ParameterizedTypeName.get(Map::class.typeName(),
+                    Integer::class.typeName(), String::class.typeName()) as TypeName
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun checkDuplicates() {
+        singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box(), duplicate = true))
+        { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.failsToCompile().withErrorContaining("Multiple methods define the same conversion")
+    }
+
+    private fun createConverter(from: TypeName, to: TypeName,
+                                typeVariables: List<TypeVariableName> = emptyList(),
+                                duplicate : Boolean = false)
+            : JavaFileObject {
+        val code = TypeSpec.classBuilder(CONVERTER).apply {
+            addTypeVariables(typeVariables)
+            addModifiers(Modifier.PUBLIC)
+            fun buildMethod(name : String) = MethodSpec.methodBuilder(name).apply {
+                addAnnotation(TypeConverter::class.java)
+                addModifiers(Modifier.PUBLIC)
+                returns(to)
+                addParameter(ParameterSpec.builder(from, "input").build())
+                if (to.isPrimitive) {
+                    addStatement("return 0")
+                } else {
+                    addStatement("return null")
+                }
+            }.build()
+            addMethod(buildMethod("convertF"))
+            if (duplicate) {
+                addMethod(buildMethod("convertF2"))
+            }
+        }.build().toString()
+        return JavaFileObjects.forSourceString(CONVERTER.toString(),
+                "package ${CONVERTER.packageName()};\n$code")
+    }
+
+    private fun singleClass(vararg jfo: JavaFileObject,
+                            handler: (CustomTypeConverter?, TestInvocation) -> Unit)
+            : CompileTester {
+        return simpleRun(*((jfo.toList() + CONTAINER).toTypedArray())) { invocation ->
+            val processed = CustomConverterProcessor.findConverters(invocation.context,
+                    invocation.processingEnv.elementUtils.getTypeElement("foo.bar.Container"))
+            handler(processed.firstOrNull()?.custom, invocation)
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
index b5f104c..bc881ea 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.support.room.processor
 
+import com.android.support.room.parser.SQLTypeAffinity
 import com.android.support.room.vo.CallType
 import com.android.support.room.vo.Field
 import com.android.support.room.vo.FieldGetter
@@ -67,11 +68,10 @@
                     name = fieldName,
                     type = intType,
                     primaryKey = true,
-                    columnName = fieldName)))
-            assertThat(field.setter, `is`(FieldSetter(setterName, intType, CallType.METHOD,
-                    field.setter.columnAdapter)))
-            assertThat(field.getter, `is`(FieldGetter(getterName, intType, CallType.METHOD,
-                    field.getter.columnAdapter)))
+                    columnName = fieldName,
+                    affinity = SQLTypeAffinity.INTEGER)))
+            assertThat(field.setter, `is`(FieldSetter(setterName, intType, CallType.METHOD)))
+            assertThat(field.getter, `is`(FieldGetter(getterName, intType, CallType.METHOD)))
         }.compilesWithoutError()
     }
 }
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
index 7e82fe3..5fa6236 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.support.room.processor
 
+import com.android.support.room.parser.SQLTypeAffinity
 import com.android.support.room.vo.CallType
 import com.android.support.room.vo.Field
 import com.android.support.room.vo.FieldGetter
@@ -46,11 +47,10 @@
                     name = "id",
                     type = intType,
                     primaryKey = true,
-                    columnName = "id")))
-            assertThat(field.setter, `is`(FieldSetter("setId", intType, CallType.METHOD,
-                    field.setter.columnAdapter)))
-            assertThat(field.getter, `is`(FieldGetter("getId", intType, CallType.METHOD,
-                    field.getter.columnAdapter)))
+                    columnName = "id",
+                    affinity = SQLTypeAffinity.INTEGER)))
+            assertThat(field.setter, `is`(FieldSetter("setId", intType, CallType.METHOD)))
+            assertThat(field.getter, `is`(FieldGetter("getId", intType, CallType.METHOD)))
             assertThat(entity.primaryKeys, `is`(listOf(field)))
         }.compilesWithoutError()
     }
@@ -232,4 +232,14 @@
         }.failsToCompile()
                 .withErrorContaining(ProcessorErrors.MISSING_PRIMARY_KEY)
     }
+
+    @Test
+    fun missingColumnAdapter() {
+        singleEntity("""
+                @PrimaryKey
+                public java.util.Date myDate;
+                """) { entity, invocation ->
+
+        }.failsToCompile().withErrorContaining(ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+    }
 }
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
index dba804c..68e5bc2 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
@@ -17,6 +17,8 @@
 package com.android.support.room.processor
 
 import com.android.support.room.Entity
+import com.android.support.room.parser.SQLTypeAffinity
+import com.android.support.room.solver.types.ColumnTypeAdapter
 import com.android.support.room.testing.TestInvocation
 import com.android.support.room.testing.TestProcessor
 import com.android.support.room.vo.Field
@@ -25,7 +27,7 @@
 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.google.testing.compile.JavaSourcesSubjectFactory
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
@@ -38,6 +40,7 @@
 import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeMirror
 
+@Suppress("HasPlatformType")
 @RunWith(JUnit4::class)
 class FieldProcessorTest {
     companion object {
@@ -52,27 +55,55 @@
                 TypeKind.INT,
                 TypeKind.BYTE,
                 TypeKind.SHORT,
-                TypeKind.INT,
                 TypeKind.LONG,
                 TypeKind.CHAR,
                 TypeKind.FLOAT,
                 TypeKind.DOUBLE)
-    }
+        val ARRAY_CONVERTER = JavaFileObjects.forSourceLines("foo.bar.MyConverter",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                public class MyConverter {
+                ${ALL_PRIMITIVES.joinToString("\n") {
+                    val arrayDef = "${it.name.toLowerCase()}[]"
+                    "@TypeConverter public static String" +
+                            " arrayIntoString($arrayDef input) { return null;}" +
+                            "@TypeConverter public static $arrayDef" +
+                            " stringIntoArray${it.name}(String input) { return null;}"
+                }}
+                ${ALL_PRIMITIVES.joinToString("\n") {
+                    val arrayDef = "${it.box()}[]"
+                    "@TypeConverter public static String" +
+                            " arrayIntoString($arrayDef input) { return null;}" +
+                            "@TypeConverter public static $arrayDef" +
+                            " stringIntoArray${it.name}Boxed(String input) { return null;}"
+                }}
+                }
+                """)
 
-    // these 2 box methods are ugly but makes tests nicer and they are private
-    private fun TypeKind.typeMirror(invocation: TestInvocation): TypeMirror {
-        return invocation.processingEnv.typeUtils.getPrimitiveType(this)
-    }
-    private fun TypeKind.box(): String {
-        return "java.lang." + when (this) {
-            TypeKind.INT -> "Integer"
-            TypeKind.CHAR -> "Character"
-            else -> this.name.toLowerCase().capitalize()
+        private fun TypeKind.box(): String {
+            return "java.lang." + when (this) {
+                TypeKind.INT -> "Integer"
+                TypeKind.CHAR -> "Character"
+                else -> this.name.toLowerCase().capitalize()
+            }
         }
-    }
 
-    private fun TypeKind.box(invocation: TestInvocation): TypeMirror {
-        return invocation.processingEnv.elementUtils.getTypeElement(box()).asType()
+        // these 2 box methods are ugly but makes tests nicer and they are private
+        private fun TypeKind.typeMirror(invocation: TestInvocation): TypeMirror {
+            return invocation.processingEnv.typeUtils.getPrimitiveType(this)
+        }
+
+        private fun TypeKind.affinity(): SQLTypeAffinity {
+            return when (this) {
+                TypeKind.FLOAT, TypeKind.DOUBLE -> SQLTypeAffinity.REAL
+                else -> SQLTypeAffinity.INTEGER
+            }
+        }
+
+        private fun TypeKind.box(invocation: TestInvocation): TypeMirror {
+            return invocation.processingEnv.elementUtils.getTypeElement(box()).asType()
+        }
     }
 
     @Test
@@ -83,7 +114,8 @@
                         Field(name = "x",
                                 type = primitive.typeMirror(invocation),
                                 primaryKey = false,
-                                element = field.element
+                                element = field.element,
+                                affinity = primitive.affinity()
                         )))
             }.compilesWithoutError()
         }
@@ -97,7 +129,8 @@
                         Field(name = "y",
                                 type = primitive.box(invocation),
                                 primaryKey = false,
-                                element = field.element)))
+                                element = field.element,
+                                affinity = primitive.affinity())))
             }.compilesWithoutError()
         }
     }
@@ -112,14 +145,15 @@
                     Field(name = "x",
                             type = TypeKind.INT.typeMirror(invocation),
                             primaryKey = true,
-                            element = field.element)))
+                            element = field.element,
+                            affinity = SQLTypeAffinity.INTEGER)))
         }.compilesWithoutError()
     }
 
     @Test
     fun columnName() {
         singleEntity("""
-            @ColumnName("foo")
+            @ColumnInfo(name = "foo")
             @PrimaryKey
             int x;
             """) { field, invocation ->
@@ -128,29 +162,51 @@
                             type = TypeKind.INT.typeMirror(invocation),
                             primaryKey = true,
                             element = field.element,
-                            columnName = "foo")))
+                            columnName = "foo",
+                            affinity = SQLTypeAffinity.INTEGER)))
         }.compilesWithoutError()
     }
 
     @Test
     fun emptyColumnName() {
         singleEntity("""
-            @ColumnName("")
+            @ColumnInfo(name = "")
             int x;
             """) { field, invocation ->
         }.failsToCompile().withErrorContaining(ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
     }
 
     @Test
+    fun byteArrayWithEnforcedType() {
+        singleEntity("@TypeConverters(foo.bar.MyConverter.class)" +
+                "@ColumnInfo(affinity = ColumnInfo.TEXT) byte[] arr;") { field, invocation ->
+            assertThat(field, `is`(Field(name = "arr",
+                    type = invocation.processingEnv.typeUtils.getArrayType(
+                            TypeKind.BYTE.typeMirror(invocation)),
+                    primaryKey = false,
+                    element = field.element,
+                    affinity = SQLTypeAffinity.TEXT)))
+            assertThat((field.cursorValueReader as? ColumnTypeAdapter)?.typeAffinity,
+                    `is`(SQLTypeAffinity.TEXT))
+        }.compilesWithoutError()
+    }
+
+    @Test
     fun primitiveArray() {
         ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("${primitive.toString().toLowerCase()}[] arr;") { field, invocation ->
+            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
+                    "${primitive.toString().toLowerCase()}[] arr;") { field, invocation ->
                 assertThat(field, `is`(
                         Field(name = "arr",
                                 type = invocation.processingEnv.typeUtils.getArrayType(
                                         primitive.typeMirror(invocation)),
                                 primaryKey = false,
-                                element = field.element)))
+                                element = field.element,
+                                affinity = if (primitive == TypeKind.BYTE) {
+                                    SQLTypeAffinity.BLOB
+                                } else {
+                                    SQLTypeAffinity.TEXT
+                                })))
             }.compilesWithoutError()
         }
     }
@@ -158,13 +214,15 @@
     @Test
     fun boxedArray() {
         ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("${primitive.box()}[] arr;") { field, invocation ->
+            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
+                    "${primitive.box()}[] arr;") { field, invocation ->
                 assertThat(field, `is`(
                         Field(name = "arr",
                                 type = invocation.processingEnv.typeUtils.getArrayType(
                                         primitive.box(invocation)),
                                 primaryKey = false,
-                                element = field.element)))
+                                element = field.element,
+                                affinity = SQLTypeAffinity.TEXT)))
             }.compilesWithoutError()
         }
     }
@@ -182,7 +240,8 @@
             assertThat(field, `is`(Field(name = "item",
                     type = TypeKind.INT.box(invocation),
                     primaryKey = false,
-                    element = field.element)))
+                    element = field.element,
+                    affinity = SQLTypeAffinity.INTEGER)))
         }.compilesWithoutError()
     }
 
@@ -200,13 +259,13 @@
     @Test
     fun nameVariations() {
         simpleRun {
-            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("x")))
-            assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("x")))
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it),
+                    false, SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
             assertThat(Field(mock(Element::class.java), "xAll",
-                            TypeKind.BOOLEAN.typeMirror(it), false).nameWithVariations,
-                    `is`(arrayListOf("xAll")))
+                    TypeKind.BOOLEAN.typeMirror(it), false, SQLTypeAffinity.INTEGER)
+                    .nameWithVariations, `is`(arrayListOf("xAll")))
         }
     }
 
@@ -214,14 +273,15 @@
     fun nameVariations_is() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("isX", "x")))
-            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("isX")))
-            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("is")))
-            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("isAllItems", "allItems")))
+            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX", "x")))
+            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX")))
+            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("is")))
+            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("isAllItems", "allItems")))
         }
     }
 
@@ -229,14 +289,15 @@
     fun nameVariations_has() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("hasX", "x")))
-            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("hasX")))
-            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("has")))
-            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("hasAllItems", "allItems")))
+            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX", "x")))
+            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX")))
+            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("has")))
+            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("hasAllItems", "allItems")))
         }
     }
 
@@ -244,18 +305,20 @@
     fun nameVariations_m() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("mall")))
-            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("mallVars")))
-            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("mAll", "all")))
-            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("m")))
-            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("mallItems")))
-            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("mAllItems", "allItems")))
+            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mall")))
+            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mallVars")))
+            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mAll", "all")))
+            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("m")))
+            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("mallItems")))
+            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("mAllItems", "allItems")))
         }
     }
 
@@ -263,21 +326,22 @@
     fun nameVariations_underscore() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("_all", "all")))
-            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("_")))
-            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it), false)
-                    .nameWithVariations, `is`(arrayListOf("_allItems", "allItems")))
+            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_all", "all")))
+            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_")))
+            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it), false,
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("_allItems", "allItems")))
         }
     }
 
     fun singleEntity(vararg input: String, handler: (Field, invocation: TestInvocation) -> Unit):
             CompileTester {
-        return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
-                .that(JavaFileObjects.forSourceString("foo.bar.MyEntity",
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
                         ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
-                ))
+                ), ARRAY_CONVERTER))
                 .processedWith(TestProcessor.builder()
                         .forAnnotations(com.android.support.room.Entity::class)
                         .nextRunHandler { invocation ->
@@ -289,10 +353,14 @@
                                                 .firstOrNull { it.kind == ElementKind.FIELD })
                                     }
                                     .first { it.second != null }
+                            val entityContext =
+                                    EntityProcessor(invocation.context, MoreElements.asType(owner))
+                                            .context
                             val parser = FieldProcessor(
-                                    baseContext = invocation.context,
+                                    baseContext = entityContext,
                                     containing = MoreTypes.asDeclared(owner.asType()),
-                                    element = field!!)
+                                    element = field!!,
+                                    bindingScope = FieldProcessor.BindingScope.TWO_WAY)
                             handler(parser.process(), invocation)
                             true
                         }
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt
index b462d76..47fe4d9 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt
@@ -17,7 +17,7 @@
 package com.android.support.room.processor
 
 import COMMON
-import com.android.support.room.ColumnName
+import com.android.support.room.ColumnInfo
 import com.android.support.room.Dao
 import com.android.support.room.Entity
 import com.android.support.room.PrimaryKey
@@ -82,7 +82,8 @@
                     name = name,
                     type = Mockito.mock(TypeMirror::class.java),
                     primaryKey = false,
-                    columnName = columnName ?: name
+                    columnName = columnName ?: name,
+                    affinity = null
             )
         }
     }
@@ -459,7 +460,7 @@
             String name;
             String lastName;
             int uid;
-            @ColumnName("ageColumn")
+            @ColumnInfo(name = "ageColumn")
             int age;
         """, listOf("*")) { adapter, queryMethod, invocation ->
             assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
@@ -470,7 +471,7 @@
     @Test
     fun pojo_nonJavaName() {
         pojoTest("""
-            @ColumnName("MAX(ageColumn)")
+            @ColumnInfo(name = "MAX(ageColumn)")
             int maxAge;
             String name;
             """, listOf("MAX(ageColumn)", "name")) { adapter, queryMethod, invocation ->
@@ -506,7 +507,7 @@
     fun pojo_badQuery() {
         // do not report mismatch if query is broken
         pojoTest("""
-            @ColumnName("MAX(ageColumn)")
+            @ColumnInfo(name = "MAX(ageColumn)")
             int maxAge;
             String name;
             """, listOf("MAX(age)", "name")) { adapter, queryMethod, invocation ->
@@ -613,7 +614,7 @@
                         DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
                 ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER))
                 .processedWith(TestProcessor.builder()
-                        .forAnnotations(Query::class, Dao::class, ColumnName::class,
+                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
                                 Entity::class, PrimaryKey::class)
                         .nextRunHandler { invocation ->
                             val (owner, methods) = invocation.roundEnv
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/solver/BasicColumnTypeAdaptersTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/solver/BasicColumnTypeAdaptersTest.kt
index 07ef67e..54fa3c1 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/solver/BasicColumnTypeAdaptersTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/solver/BasicColumnTypeAdaptersTest.kt
@@ -42,7 +42,7 @@
     val scope = testCodeGenScope()
 
     companion object {
-        val SQLITE_STMT : TypeName = ClassName.get("android.database.sqlite", "SQLiteStatement")
+        val SQLITE_STMT: TypeName = ClassName.get("android.database.sqlite", "SQLiteStatement")
         val CURSOR: TypeName = ClassName.get("android.database", "Cursor")
 
         @Parameterized.Parameters(name = "kind:{0},bind:_{1},cursor:_{2}")
@@ -87,7 +87,7 @@
     fun bind() {
         simpleRun { invocation ->
             val adapter = TypeAdapterStore(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv))!!
+                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
             adapter.bindToStmt("st", "6", "inp", scope)
             assertThat(scope.generate().trim(), `is`(bindCode))
             generateCode(invocation, false)
@@ -101,7 +101,8 @@
         }
         simpleRun { invocation ->
             val adapter = TypeAdapterStore(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(input.getBoxedTypeMirror(invocation.processingEnv))!!
+                    .findColumnTypeAdapter(
+                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
             adapter.bindToStmt("st", "6", "inp", scope)
             assertThat(scope.generate().trim(), `is`(
                     """
@@ -116,9 +117,9 @@
         }.compilesWithoutError()
     }
 
-    private fun generateCode(invocation: TestInvocation, boxed : Boolean) {
+    private fun generateCode(invocation: TestInvocation, boxed: Boolean) {
         val typeMirror = if (boxed) input.getBoxedTypeMirror(invocation.processingEnv)
-                         else input.getTypeMirror(invocation.processingEnv)
+        else input.getTypeMirror(invocation.processingEnv)
         val spec = TypeSpec.classBuilder("OutClass")
                 .addField(FieldSpec.builder(SQLITE_STMT, "st").build())
                 .addField(FieldSpec.builder(CURSOR, "crs").build())
@@ -137,7 +138,7 @@
     fun read() {
         simpleRun { invocation ->
             val adapter = TypeAdapterStore(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv))!!
+                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
             adapter.readFromCursor("out", "crs", "9", scope)
             assertThat(scope.generate().trim(), `is`(cursorCode))
             generateCode(invocation, false)
@@ -151,7 +152,8 @@
         }
         simpleRun { invocation ->
             val adapter = TypeAdapterStore(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(input.getBoxedTypeMirror(invocation.processingEnv))!!
+                    .findColumnTypeAdapter(
+                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
             adapter.readFromCursor("out", "crs", "9", scope)
             assertThat(scope.generate().trim(), `is`(
                     """
@@ -166,8 +168,8 @@
         }.compilesWithoutError()
     }
 
-    data class Input(val typeKind: TypeKind, val qName : String? = null) {
-        fun getTypeMirror(processingEnv: ProcessingEnvironment) : TypeMirror {
+    data class Input(val typeKind: TypeKind, val qName: String? = null) {
+        fun getTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
             return if (typeKind.isPrimitive) {
                 processingEnv.typeUtils.getPrimitiveType(typeKind)
             } else {
@@ -175,7 +177,7 @@
             }
         }
 
-        fun getBoxedTypeMirror(processingEnv: ProcessingEnvironment) : TypeMirror {
+        fun getBoxedTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
             return if (typeKind.isPrimitive) {
                 processingEnv.typeUtils
                         .boxedClass(getTypeMirror(processingEnv) as PrimitiveType)
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/solver/CustomTypeConverterResolutionTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/solver/CustomTypeConverterResolutionTest.kt
new file mode 100644
index 0000000..92b8926
--- /dev/null
+++ b/room/compiler/src/test/kotlin/com/android/support/room/solver/CustomTypeConverterResolutionTest.kt
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+@file:Suppress("HasPlatformType")
+
+package com.android.support.room.solver
+
+import com.android.support.room.Dao
+import com.android.support.room.Database
+import com.android.support.room.Entity
+import com.android.support.room.PrimaryKey
+import com.android.support.room.Query
+import com.android.support.room.RoomProcessor
+import com.android.support.room.TypeConverter
+import com.android.support.room.TypeConverters
+import com.android.support.room.ext.RoomTypeNames
+import com.android.support.room.ext.S
+import com.android.support.room.ext.T
+import com.android.support.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
+import com.android.support.room.testing.TestInvocation
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class CustomTypeConverterResolutionTest {
+    fun TypeSpec.toJFO() : JavaFileObject {
+        return JavaFileObjects.forSourceString("foo.bar.${this.name}",
+                "package foo.bar;\n" + toString())
+    }
+
+    companion object {
+        val ENTITY = ClassName.get("foo.bar", "MyEntity")
+        val DB = ClassName.get("foo.bar", "MyDb")
+        val DAO = ClassName.get("foo.bar", "MyDao")
+
+        val CUSTOM_TYPE = ClassName.get("foo.bar", "CustomType")
+        val CUSTOM_TYPE_JFO = JavaFileObjects.forSourceLines(CUSTOM_TYPE.toString(),
+                """
+                package ${CUSTOM_TYPE.packageName()};
+                public class ${CUSTOM_TYPE.simpleName()} {
+                    public int value;
+                }
+                """)
+        val CUSTOM_TYPE_CONVERTER = ClassName.get("foo.bar", "MyConverter")
+        val CUSTOM_TYPE_CONVERTER_JFO = JavaFileObjects.forSourceLines(
+                CUSTOM_TYPE_CONVERTER.toString(),
+                """
+                package ${CUSTOM_TYPE_CONVERTER.packageName()};
+                public class ${CUSTOM_TYPE_CONVERTER.simpleName()} {
+                    @${TypeConverter::class.java.canonicalName}
+                    public static $CUSTOM_TYPE toCustom(int value) {
+                        return null;
+                    }
+                    @${TypeConverter::class.java.canonicalName}
+                    public static int fromCustom($CUSTOM_TYPE input) {
+                        return 0;
+                    }
+                }
+                """)
+    }
+
+    @Test
+    fun useFromDatabase_forEntity() {
+        val entity = createEntity(hasCustomField = true)
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true, hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDatabase_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDatabase_forReturnValue() {
+        val entity = createEntity(hasCustomField = true)
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDao_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasConverters = true, hasQueryReturningEntity = true,
+                hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntity_forReturnValue() {
+        val entity = createEntity(hasCustomField = true, hasConverters = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntityField_forReturnValue() {
+        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntity_forQueryParameter() {
+        val entity = createEntity(hasCustomField = true, hasConverters = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+    }
+
+    @Test
+    fun useFromEntityField_forQueryParameter() {
+        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+    }
+
+    @Test
+    fun useFromQueryMethod_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true, hasMethodConverters = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromQueryParameter_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true, hasParameterConverters = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    fun run(vararg jfos : JavaFileObject, f: (TestInvocation) -> Unit): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfos.toList() + CUSTOM_TYPE_JFO + CUSTOM_TYPE_CONVERTER_JFO)
+                .processedWith(RoomProcessor())
+    }
+
+    private fun createEntity(hasCustomField : Boolean = false,
+                             hasConverters : Boolean = false,
+                             hasConverterOnField : Boolean = false) : TypeSpec {
+        if (hasConverterOnField && hasConverters) {
+            throw IllegalArgumentException("cannot have both converters")
+        }
+        return TypeSpec.classBuilder(ENTITY).apply {
+            addAnnotation(Entity::class.java)
+            addModifiers(Modifier.PUBLIC)
+            if (hasCustomField) {
+                addField(FieldSpec.builder(CUSTOM_TYPE, "myCustomField", Modifier.PUBLIC).apply {
+                    if (hasConverterOnField) {
+                        addAnnotation(createConvertersAnnotation())
+                    }
+                }.build())
+            }
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
+                addAnnotation(PrimaryKey::class.java)
+            }.build())
+        }.build()
+    }
+
+    private fun createDatabase(hasConverters : Boolean = false,
+                               hasDao : Boolean = false) : TypeSpec {
+        return TypeSpec.classBuilder(DB).apply {
+            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
+            superclass(RoomTypeNames.ROOM_DB)
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
+                addAnnotation(PrimaryKey::class.java)
+            }.build())
+            if (hasDao) {
+                addMethod(MethodSpec.methodBuilder("getDao").apply {
+                    addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+                    returns(DAO)
+                }.build())
+            }
+            addAnnotation(
+                    AnnotationSpec.builder(Database::class.java).apply {
+                        addMember("entities", "{$T.class}", ENTITY)
+                    }.build()
+            )
+        }.build()
+    }
+
+    private fun createDao(hasConverters : Boolean = false,
+                          hasQueryReturningEntity : Boolean = false,
+                          hasQueryWithCustomParam : Boolean = false,
+                          hasMethodConverters : Boolean = false,
+                          hasParameterConverters : Boolean = false) : TypeSpec {
+        val annotationCount = listOf(hasMethodConverters, hasConverters, hasParameterConverters)
+                .map { if (it) 1 else 0 }.sum()
+        if (annotationCount > 1) {
+            throw IllegalArgumentException("cannot set both of these")
+        }
+        if (hasParameterConverters && !hasQueryWithCustomParam) {
+            throw IllegalArgumentException("inconsistent")
+        }
+        return TypeSpec.classBuilder(DAO).apply {
+            addAnnotation(Dao::class.java)
+            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            if (hasQueryReturningEntity) {
+                addMethod(MethodSpec.methodBuilder("loadAll").apply {
+                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
+                        addMember("value", S, "SELECT * FROM ${ENTITY.simpleName()} LIMIT 1")
+                    }.build())
+                    addModifiers(Modifier.ABSTRACT)
+                    returns(ENTITY)
+                }.build())
+            }
+            if (hasQueryWithCustomParam) {
+                addMethod(MethodSpec.methodBuilder("queryWithCustom").apply {
+                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
+                        addMember("value", S, "SELECT COUNT(*) FROM ${ENTITY.simpleName()} where" +
+                                " id IN(?)")
+                    }.build())
+                    if (hasMethodConverters) {
+                        addAnnotation(createConvertersAnnotation())
+                    }
+                    addParameter(ParameterSpec.builder(CUSTOM_TYPE, "customs").apply {
+                        if (hasParameterConverters) {
+                            addAnnotation(createConvertersAnnotation())
+                        }
+                    }.build())
+                    addModifiers(Modifier.ABSTRACT)
+                    returns(TypeName.INT)
+                }.build())
+            }
+        }.build()
+    }
+
+    private fun createConvertersAnnotation(): AnnotationSpec {
+        return AnnotationSpec.builder(TypeConverters::class.java)
+                .addMember("value", "$T.class", CUSTOM_TYPE_CONVERTER).build()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/solver/TypeAdapterStoreTest.kt
index 0741c1e..8268d9d 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/solver/TypeAdapterStoreTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/solver/TypeAdapterStoreTest.kt
@@ -53,7 +53,7 @@
         singleRun { invocation ->
             val store = TypeAdapterStore(Context(invocation.processingEnv))
             val primitiveType = invocation.processingEnv.typeUtils.getPrimitiveType(TypeKind.INT)
-            val adapter = store.findColumnTypeAdapter(primitiveType)
+            val adapter = store.findColumnTypeAdapter(primitiveType, null)
             assertThat(adapter, notNullValue())
         }.compilesWithoutError()
     }
@@ -64,7 +64,7 @@
             val store = TypeAdapterStore(Context(invocation.processingEnv))
             val booleanType = invocation.processingEnv.typeUtils
                     .getPrimitiveType(TypeKind.BOOLEAN)
-            val adapter = store.findColumnTypeAdapter(booleanType)
+            val adapter = store.findColumnTypeAdapter(booleanType, null)
             assertThat(adapter, notNullValue())
             assertThat(adapter, instanceOf(CompositeAdapter::class.java))
             val bindScope = testCodeGenScope()
@@ -92,7 +92,7 @@
                     pointTypeConverters(invocation.processingEnv))
             val pointType = invocation.processingEnv.elementUtils
                     .getTypeElement("foo.bar.Point").asType()
-            val adapter = store.findColumnTypeAdapter(pointType)
+            val adapter = store.findColumnTypeAdapter(pointType, null)
             assertThat(adapter, notNullValue())
             assertThat(adapter, instanceOf(CompositeAdapter::class.java))
 
@@ -125,7 +125,7 @@
             val store = TypeAdapterStore(Context(invocation.processingEnv), binders[0],
                     binders[1])
 
-            val adapter = store.findColumnTypeAdapter(binders[0].from)
+            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
             assertThat(adapter, notNullValue())
 
             val bindScope = testCodeGenScope()
@@ -153,10 +153,10 @@
         singleRun { invocation ->
             val binders = createIntListToStringBinders(invocation)
             val store = TypeAdapterStore(Context(invocation.processingEnv), binders[0])
-            val adapter = store.findColumnTypeAdapter(binders[0].from)
+            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
             assertThat(adapter, nullValue())
 
-            val stmtBinder = store.findStatementValueBinder(binders[0].from)
+            val stmtBinder = store.findStatementValueBinder(binders[0].from, null)
             assertThat(stmtBinder, notNullValue())
 
             val converter = store.findTypeConverter(binders[0].from,
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/solver/query/QueryWriterTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/solver/query/QueryWriterTest.kt
index d14f95d..2e92868 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/solver/query/QueryWriterTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/solver/query/QueryWriterTest.kt
@@ -21,7 +21,6 @@
 import com.android.support.room.ext.RoomTypeNames.ROOM_SQL_QUERY
 import com.android.support.room.ext.RoomTypeNames.STRING_UTIL
 import com.android.support.room.processor.QueryMethodProcessor
-import com.android.support.room.solver.CodeGenScope
 import com.android.support.room.testing.TestProcessor
 import com.android.support.room.writer.QueryWriter
 import com.google.auto.common.MoreElements
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/testing/test_util.kt b/room/compiler/src/test/kotlin/com/android/support/room/testing/test_util.kt
index ebc1bca..3e21756 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/testing/test_util.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/testing/test_util.kt
@@ -27,9 +27,8 @@
 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.google.testing.compile.JavaSourcesSubjectFactory
 import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeSpec
 import org.mockito.Mockito
 import java.io.File
 import javax.lang.model.element.Element
@@ -59,14 +58,13 @@
                 LifecyclesTypeNames.COMPUTABLE_LIVE_DATA.toString())
     }
 }
-
 fun testCodeGenScope(): CodeGenScope {
     return CodeGenScope(Mockito.mock(ClassWriter::class.java))
 }
 
-fun simpleRun(f: (TestInvocation) -> Unit): CompileTester {
-    return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
-            .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
+fun simpleRun(vararg jfos : JavaFileObject, f: (TestInvocation) -> Unit): CompileTester {
+    return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+            .that(jfos.toList() + JavaFileObjects.forSourceString("foo.bar.MyClass",
                     """
                     package foo.bar;
                     abstract public class MyClass {
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt
index 6dd502c..8c160dc 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt
@@ -61,7 +61,7 @@
         }.compilesWithoutError()
     }
 
-    fun createVerifier(invocation : TestInvocation) : DatabaseVerifier {
+    fun createVerifier(invocation: TestInvocation): DatabaseVerifier {
         return DatabaseVerifier.create(invocation.context, mock(Element::class.java),
                 userDb(invocation.context).entities)!!
     }
@@ -168,10 +168,10 @@
 
     private fun userDb(context: Context): Database {
         return database(entity("User",
-                primaryField(context, "id", primitive(context, TypeKind.INT)),
-                field(context, "name", context.COMMON_TYPES.STRING),
-                field(context, "lastName", context.COMMON_TYPES.STRING),
-                field(context, "ratio", primitive(context, TypeKind.FLOAT))))
+                primaryField("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER),
+                field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                field("ratio", primitive(context, TypeKind.FLOAT), SQLTypeAffinity.REAL)))
     }
 
     private fun database(vararg entities: Entity): Database {
@@ -182,7 +182,8 @@
                 daoMethods = emptyList())
     }
 
-    private fun entity(tableName: String, vararg fields: Field): Entity {
+    private fun entity(tableName: String, vararg fields: Field)
+            : Entity {
         return Entity(
                 element = mock(TypeElement::class.java),
                 tableName = tableName,
@@ -191,35 +192,35 @@
         )
     }
 
-    private fun field(context: Context, name: String, type: TypeMirror): Field {
+    private fun field(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field {
         val f = Field(
                 element = mock(Element::class.java),
                 name = name,
                 type = type,
                 primaryKey = false,
-                columnName = name
+                columnName = name,
+                affinity = affinity
         )
-        assignGetterSetter(context, f, name, type)
+        assignGetterSetter(f, name, type)
         return f
     }
 
-    private fun primaryField(context: Context, name: String, type: TypeMirror): Field {
+    private fun primaryField(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field {
         val f = Field(
                 element = mock(Element::class.java),
                 name = name,
                 type = type,
                 primaryKey = true,
-                columnName = name
+                columnName = name,
+                affinity = affinity
         )
-        assignGetterSetter(context, f, name, type)
+        assignGetterSetter(f, name, type)
         return f
     }
 
-    private fun assignGetterSetter(context: Context, f: Field, name: String, type: TypeMirror) {
-        f.getter = FieldGetter(name, type, CallType.FIELD,
-                context.typeAdapterStore.findColumnTypeAdapter(f.type))
-        f.setter = FieldSetter(name, type, CallType.FIELD,
-                context.typeAdapterStore.findColumnTypeAdapter(f.type))
+    private fun assignGetterSetter(f: Field, name: String, type: TypeMirror) {
+        f.getter = FieldGetter(name, type, CallType.FIELD)
+        f.setter = FieldSetter(name, type, CallType.FIELD)
     }
 
     private fun primitive(context: Context, kind: TypeKind): PrimitiveType {
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt
index 5008040..393613f 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt
@@ -19,9 +19,12 @@
 import com.android.support.room.processor.BaseEntityParserTest
 import com.google.testing.compile.CompileTester
 import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeSpec
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
 
 @RunWith(JUnit4::class)
 class EntityCursorConverterWriterTest : BaseEntityParserTest() {
@@ -29,10 +32,8 @@
         val OUT_PREFIX = """
             package foo.bar;
             import android.database.Cursor;
-            import com.android.support.room.CursorConverter;
-            import java.lang.Override;
             import java.lang.String;
-            public class MyEntity_CursorConverter implements CursorConverter<MyEntity> {
+            public class MyContainerClass {
             """.trimIndent()
         const val OUT_SUFFIX = "}"
     }
@@ -50,8 +51,7 @@
                 public void setId(int id) { this.id = id; }
                 """,
                 """
-                @Override
-                public MyEntity convert(Cursor cursor) {
+                private MyEntity __entityCursorConverter_fooBarMyEntity(Cursor cursor) {
                   MyEntity _entity = new MyEntity();
                   int _columnIndex = 0;
                   for (String _columnName : cursor.getColumnNames()) {
@@ -88,7 +88,6 @@
 
     fun generateAndMatch(input: String, output : String,
                          attributes: Map<String, String> = mapOf()) {
-
         generate(input, attributes)
                 .compilesWithoutError()
                 .and()
@@ -99,7 +98,16 @@
 
     fun generate(input: String, attributes: Map<String, String> = mapOf()) : CompileTester {
         return singleEntity(input, attributes) { entity, invocation ->
-            EntityCursorConverterWriter(entity).write(invocation.processingEnv)
+            val className = ClassName.get("foo.bar","MyContainerClass")
+            val writer = object : ClassWriter(className){
+                override fun createTypeSpecBuilder(): TypeSpec.Builder {
+                    getOrCreateMethod(EntityCursorConverterWriter(entity))
+                    return TypeSpec.classBuilder(className).apply {
+                        addModifiers(Modifier.PUBLIC)
+                    }
+                }
+            }
+            writer.write(invocation.processingEnv)
         }
     }
 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java
index 5599973..89f7da6 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java
@@ -18,10 +18,33 @@
 
 import com.android.support.room.Database;
 import com.android.support.room.RoomDatabase;
+import com.android.support.room.TypeConverter;
+import com.android.support.room.TypeConverters;
 import com.android.support.room.integration.testapp.dao.UserDao;
 import com.android.support.room.integration.testapp.vo.User;
 
+import java.util.Date;
+
 @Database(entities = User.class)
+@TypeConverters(TestDatabase.Converters.class)
 public abstract class TestDatabase extends RoomDatabase {
     public abstract UserDao getUserDao();
+
+    @SuppressWarnings("unused")
+    public static class Converters {
+        @TypeConverter
+        public Date fromTimestamp(Long value) {
+            return value == null ? null : new Date(value);
+        }
+
+        @TypeConverter
+        public Long dateToTimestamp(Date date) {
+            if (date == null) {
+                return null;
+            } else {
+                return date.getTime();
+            }
+        }
+    }
+
 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/dao/UserDao.java
index c892086..c72bbff 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/dao/UserDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/dao/UserDao.java
@@ -24,6 +24,7 @@
 import com.android.support.room.integration.testapp.vo.AvgWeightByAge;
 import com.android.support.room.integration.testapp.vo.User;
 
+import java.util.Date;
 import java.util.List;
 
 @SuppressWarnings("SameParameterValue")
@@ -91,4 +92,7 @@
 
     @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC LIMIT 1")
     LiveData<AvgWeightByAge> maxWeightByAgeGroup();
+
+    @Query("select * from user where mBirthday > :from AND mBirthday < :to")
+    List<User> findByBirthdayRange(Date from, Date to);
 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index f5e2c8f..673501b 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -41,6 +41,7 @@
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 
 @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
@@ -196,4 +197,16 @@
         mUserDao.insert(user);
         assertThat(mUserDao.findByAge(19), is(Collections.singletonList(user)));
     }
+
+    @Test
+    public void customConverterField() {
+        User user = TestUtil.createUser(20);
+        Date theDate = new Date(System.currentTimeMillis() - 200);
+        user.setBirthday(theDate);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime() - 100),
+                new Date(theDate.getTime() + 1)).get(0), is(user));
+        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime()),
+                new Date(theDate.getTime() + 1)).size(), is(0));
+    }
 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java
index 7bf1715..a9d93d1 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java
@@ -16,14 +16,14 @@
 
 package com.android.support.room.integration.testapp.vo;
 
-import com.android.support.room.ColumnName;
+import com.android.support.room.ColumnInfo;
 
 @SuppressWarnings("unused")
 public class AvgWeightByAge {
 
     private int mAge;
 
-    @ColumnName("AVG(mWeight)")
+    @ColumnInfo(name = "AVG(mWeight)")
     private float mWeight;
 
     public AvgWeightByAge() {
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/User.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/User.java
index da0ab17..8a78922 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/User.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/User.java
@@ -18,8 +18,13 @@
 
 import com.android.support.room.Entity;
 import com.android.support.room.PrimaryKey;
+import com.android.support.room.TypeConverters;
+import com.android.support.room.integration.testapp.TestDatabase;
+
+import java.util.Date;
 
 @Entity
+@TypeConverters({TestDatabase.Converters.class})
 public class User {
 
     @PrimaryKey
@@ -35,6 +40,8 @@
 
     private float mWeight;
 
+    private Date mBirthday;
+
     public int getId() {
         return mId;
     }
@@ -83,6 +90,14 @@
         mWeight = weight;
     }
 
+    public Date getBirthday() {
+        return mBirthday;
+    }
+
+    public void setBirthday(Date birthday) {
+        mBirthday = birthday;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -110,7 +125,10 @@
         if (mName != null ? !mName.equals(user.mName) : user.mName != null) {
             return false;
         }
-        return mLastName != null ? mLastName.equals(user.mLastName) : user.mLastName == null;
+        if (mLastName != null ? !mLastName.equals(user.mLastName) : user.mLastName != null) {
+            return false;
+        }
+        return mBirthday != null ? mBirthday.equals(user.mBirthday) : user.mBirthday == null;
     }
 
     @Override
@@ -121,6 +139,7 @@
         result = 31 * result + mAge;
         result = 31 * result + (mAdmin ? 1 : 0);
         result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
+        result = 31 * result + (mBirthday != null ? mBirthday.hashCode() : 0);
         return result;
     }
 
@@ -133,6 +152,7 @@
                 + ", mAge=" + mAge
                 + ", mAdmin=" + mAdmin
                 + ", mWeight=" + mWeight
+                + ", mBirthday=" + mBirthday
                 + '}';
     }
 }
diff --git a/room/runtime/src/main/java/com/android/support/room/CursorConverter.java b/room/runtime/src/main/java/com/android/support/room/CursorConverter.java
deleted file mode 100644
index 73e649e..0000000
--- a/room/runtime/src/main/java/com/android/support/room/CursorConverter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 android.database.Cursor;
-
-/**
- * Converts a Cursor into an instance of type T.
- * @param <T> The type of the output class.
- *
- * @hide
- */
-@SuppressWarnings("unused")
-public interface CursorConverter<T> {
-    /**
-     * Converts the cursor into an instance of type T.
-     * <p>
-     * This method should NOT advance / move the cursor.
-     *
-     * @param cursor The cursor
-     * @return An instance of type T.
-     */
-    T convert(Cursor cursor);
-}
diff --git a/room/runtime/src/main/java/com/android/support/room/Room.java b/room/runtime/src/main/java/com/android/support/room/Room.java
index 95bbd60..d16ba69 100644
--- a/room/runtime/src/main/java/com/android/support/room/Room.java
+++ b/room/runtime/src/main/java/com/android/support/room/Room.java
@@ -19,9 +19,6 @@
 import android.content.Context;
 import android.support.annotation.NonNull;
 
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Utility class for Room runtime.
  */
@@ -29,17 +26,16 @@
 public class Room {
     static final String LOG_TAG = "ROOM";
     private static final String CURSOR_CONV_SUFFIX = "_CursorConverter";
-    private static Map<Class, CursorConverter> sCursorConverterCache = new HashMap<>();
 
     /**
      * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
      * should keep a reference to it and re-use.
      *
      * @param context The context for the database. This is usually the Application context.
-     * @param klass The abstract class which is annotated with {@link Database} and extends
-     * {@link RoomDatabase}.
-     * @param name The name of the database file.
-     * @param <T> The type of the database class.
+     * @param klass   The abstract class which is annotated with {@link Database} and extends
+     *                {@link RoomDatabase}.
+     * @param name    The name of the database file.
+     * @param <T>     The type of the database class.
      * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
      */
     @SuppressWarnings("WeakerAccess")
@@ -60,9 +56,9 @@
      * Once a database is built, you should keep a reference to it and re-use.
      *
      * @param context The context for the database. This is usually the Application context.
-     * @param klass The abstract class which is annotated with {@link Database} and extends
-     * {@link RoomDatabase}.
-     * @param <T> The type of the database class.
+     * @param klass   The abstract class which is annotated with {@link Database} and extends
+     *                {@link RoomDatabase}.
+     * @param <T>     The type of the database class.
      * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
      */
     public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
@@ -70,24 +66,6 @@
         return new RoomDatabase.Builder<>(context, klass, null);
     }
 
-    /**
-     * Returns the CursorConverter for the given type.
-     *
-     * @param klass The class to convert from Cursor
-     * @param <T> The type parameter of the class
-     * @return A CursorConverter that can create an instance of the given klass from a Cursor.
-     */
-    public static <T> CursorConverter<T> getConverter(Class<T> klass) {
-        CursorConverter existing = sCursorConverterCache.get(klass);
-        if (existing != null) {
-            //noinspection unchecked
-            return existing;
-        }
-        CursorConverter<T> generated = getGeneratedImplementation(klass, CURSOR_CONV_SUFFIX);
-        sCursorConverterCache.put(klass, generated);
-        return generated;
-    }
-
     @NonNull
     static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
         //noinspection TryWithIdenticalCatches