From 5ed18e6a350aab1ab349f61f3514e87e968ba52c Mon Sep 17 00:00:00 2001
From: Dmitry Nikitin <nikitin.da.90@gmail.com>
Date: Wed, 11 Nov 2015 01:54:47 +0300
Subject: [PATCH] Add module with common annotations processing logic

---
 build.gradle                                       |   3 +-
 settings.gradle                                    |   5 +-
 storio-common-annotations-processor/build.gradle   |  16 ++
 .../gradle.properties                              |   3 +
 .../annotations/processor/ProcessingException.java |  31 +++
 .../processor/StorIOAnnotationsProcessor.java      | 225 +++++++++++++++++++++
 .../annotations/processor/generate/Common.java     |  10 +
 .../processor/generate/ResolverGenerator.java      |  12 ++
 .../processor/introspection/JavaType.java          |  62 ++++++
 .../processor/introspection/StorIOColumnMeta.java  |  73 +++++++
 .../processor/introspection/StorIOTypeMeta.java    |  67 ++++++
 .../processor/introspection/JavaTypeTest.java      | 126 ++++++++++++
 .../build.gradle                                   |   1 +
 .../annotations/processor/ProcessingException.java |  31 ---
 .../processor/StorIOContentResolverProcessor.java  | 152 ++++----------
 .../annotations/processor/generate/Common.java     |  10 -
 .../generate/DeleteResolverGenerator.java          |   9 +-
 .../processor/generate/GetResolverGenerator.java   |  41 ++--
 .../processor/generate/PutResolverGenerator.java   |  13 +-
 .../processor/generate/QueryGenerator.java         |   6 +-
 .../processor/introspection/JavaType.java          |  62 ------
 .../StorIOContentResolverColumnMeta.java           |  63 +-----
 .../StorIOContentResolverTypeMeta.java             |  60 +-----
 .../generate/GetResolverGeneratorTest.java         |   2 +-
 .../processor/introspection/JavaTypeTest.java      | 126 ------------
 storio-sqlite-annotations-processor/build.gradle   |   1 +
 .../annotations/processor/ProcessingException.java |  31 ---
 .../processor/StorIOSQLiteProcessor.java           | 196 ++++--------------
 .../annotations/processor/generate/Common.java     |  10 -
 .../generate/DeleteResolverGenerator.java          |   9 +-
 .../processor/generate/GetResolverGenerator.java   |  41 ++--
 .../processor/generate/PutResolverGenerator.java   |  13 +-
 .../processor/generate/QueryGenerator.java         |   6 +-
 .../processor/introspection/JavaType.java          |  62 ------
 .../introspection/StorIOSQLiteColumnMeta.java      |  67 +-----
 .../introspection/StorIOSQLiteTypeMeta.java        |  64 +-----
 .../generate/GetResolverGeneratorTest.java         |   2 +-
 .../processor/introspection/JavaTypeTest.java      | 126 ------------
 38 files changed, 812 insertions(+), 1025 deletions(-)
 create mode 100644 storio-common-annotations-processor/build.gradle
 create mode 100644 storio-common-annotations-processor/gradle.properties
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/ProcessingException.java
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/StorIOAnnotationsProcessor.java
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/Common.java
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/ResolverGenerator.java
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOColumnMeta.java
 create mode 100644 storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOTypeMeta.java
 create mode 100644 storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java
 delete mode 100644 storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/ProcessingException.java
 delete mode 100644 storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/Common.java
 delete mode 100644 storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaType.java
 delete mode 100644 storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaTypeTest.java
 delete mode 100644 storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/ProcessingException.java
 delete mode 100644 storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/Common.java
 delete mode 100644 storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaType.java
 delete mode 100644 storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaTypeTest.java

diff --git a/build.gradle b/build.gradle
index 61a4279..0645c60 100644
--- a/build.gradle
+++ b/build.gradle
@@ -74,8 +74,9 @@ ext.libraries = [
         storIOSQLite                              : project(':storio-sqlite'),
         storIOContentResolver                     : project(':storio-content-resolver'),
         storIOSQLiteAnnotations                   : project(':storio-sqlite-annotations'),
-        storIOSQLiteAnnotationsProcessor          : project(':storio-sqlite-annotations-processor'),
         storIOContentResolverAnnotations          : project(':storio-content-resolver-annotations'),
+        storIOCommonAnnotationsProcessor          : project(':storio-common-annotations-processor'),
+        storIOSQLiteAnnotationsProcessor          : project(':storio-sqlite-annotations-processor'),
         storIOContentResolverAnnotationsProcessor : project(':storio-content-resolver-annotations-processor'),
         storIOTestCommon                          : project(':storio-test-common'),
 
diff --git a/settings.gradle b/settings.gradle
index 7f9a215..311861c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,9 +4,10 @@ include ':storio-sqlite'
 include ':storio-content-resolver'
 
 include ':storio-sqlite-annotations'
-include ':storio-sqlite-annotations-processor'
-
 include ':storio-content-resolver-annotations'
+
+include ':storio-common-annotations-processor'
+include ':storio-sqlite-annotations-processor'
 include ':storio-content-resolver-annotations-processor'
 
 include ':storio-test-without-rxjava'
diff --git a/storio-common-annotations-processor/build.gradle b/storio-common-annotations-processor/build.gradle
new file mode 100644
index 0000000..ac0cb93
--- /dev/null
+++ b/storio-common-annotations-processor/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+targetCompatibility = '1.6'
+sourceCompatibility = '1.6'
+
+dependencies {
+    compile libraries.intellijAnnotations
+    compile libraries.autoService
+    compile libraries.javaPoet
+
+    testCompile libraries.junit
+    testCompile libraries.assertJ
+    testCompile libraries.mockitoCore
+}
+
+apply from: '../gradle/publish-java-lib.gradle'
\ No newline at end of file
diff --git a/storio-common-annotations-processor/gradle.properties b/storio-common-annotations-processor/gradle.properties
new file mode 100644
index 0000000..1b5433c
--- /dev/null
+++ b/storio-common-annotations-processor/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=common-annotations-processor
+POM_ARTIFACT_ID=common-annotations-processor
+POM_PACKAGING=jar
\ No newline at end of file
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/ProcessingException.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/ProcessingException.java
new file mode 100644
index 0000000..b26942e
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/ProcessingException.java
@@ -0,0 +1,31 @@
+package com.pushtorefresh.storio.common.annotations.processor;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.lang.model.element.Element;
+
+/**
+ * Useful for logging errors from AnnotationProcessor,
+ * stores reference to {@link Element} that caused exception so IDE will show developer where is the problem
+ */
+public class ProcessingException extends RuntimeException {
+
+    /**
+     * Element that caused exception
+     */
+    @NotNull
+    private final Element element;
+
+    public ProcessingException(@NotNull Element element, @NotNull String message) {
+        super(message);
+        this.element = element;
+    }
+
+    /**
+     * @return non-null element that caused exception
+     */
+    @NotNull
+    public Element element() {
+        return element;
+    }
+}
\ No newline at end of file
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/StorIOAnnotationsProcessor.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/StorIOAnnotationsProcessor.java
new file mode 100644
index 0000000..70d38ca
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/StorIOAnnotationsProcessor.java
@@ -0,0 +1,225 @@
+package com.pushtorefresh.storio.common.annotations.processor;
+
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOColumnMeta;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOTypeMeta;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+import static javax.lang.model.element.ElementKind.CLASS;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+/**
+ * Base annotation processor for StorIO
+ * <p>
+ * It'll process annotations to generate StorIO Object-Mapping
+ * <p>
+ * Addition: Annotation Processor should work fast and be optimized because it's part of compilation
+ * We don't want to annoy developers, who use StorIO
+ */
+// Generate file with annotation processor declaration via another Annotation Processor!
+public abstract class StorIOAnnotationsProcessor
+        <TypeMeta extends StorIOTypeMeta, ColumnMeta extends StorIOColumnMeta>
+        extends AbstractProcessor {
+
+    private Filer filer;
+    private Elements elementUtils;
+    private Messager messager;
+
+    /**
+     * Processes class annotations
+     *
+     * @param roundEnvironment environment
+     * @return non-null unmodifiable map(element, typeMeta)
+     */
+    @NotNull
+    private Map<TypeElement, TypeMeta> processAnnotatedClasses(@NotNull final RoundEnvironment roundEnvironment, @NotNull final Elements elementUtils) {
+        final Set<? extends Element> elementsAnnotatedWithStorIOType
+                = roundEnvironment.getElementsAnnotatedWith(getTypeAnnotationClass());
+
+        final Map<TypeElement, TypeMeta> results
+                = new HashMap<TypeElement, TypeMeta>(elementsAnnotatedWithStorIOType.size());
+
+        for (final Element annotatedElement : elementsAnnotatedWithStorIOType) {
+            final TypeElement classElement = validateAnnotatedClass(annotatedElement);
+            final TypeMeta typeMeta = processAnnotatedClass(classElement, elementUtils);
+            results.put(classElement, typeMeta);
+        }
+
+        return Collections.unmodifiableMap(results);
+    }
+
+    /**
+     * Checks that annotated element satisfies all required conditions
+     *
+     * @param annotatedElement an annotated type
+     * @return {@link TypeElement} object
+     */
+    @NotNull
+    private TypeElement validateAnnotatedClass(@NotNull final Element annotatedElement) {
+        // we expect here that annotatedElement is Class, annotation requires that via @Target
+        final TypeElement annotatedTypeElement = (TypeElement) annotatedElement;
+
+        if (annotatedTypeElement.getModifiers().contains(PRIVATE)) {
+            throw new ProcessingException(
+                    annotatedElement,
+                    getTypeAnnotationClass().getSimpleName() + " can not be applied to private class: " + annotatedTypeElement.getQualifiedName()
+            );
+        }
+
+        return annotatedTypeElement;
+    }
+
+    /**
+     * Checks that element annotated with {@link StorIOColumnMeta} satisfies all required conditions
+     *
+     * @param annotatedField an annotated field
+     */
+    protected void validateAnnotatedField(@NotNull final Element annotatedField) {
+        // we expect here that annotatedElement is Field, annotation requires that via @Target
+
+        final Element enclosingElement = annotatedField.getEnclosingElement();
+
+        if (!enclosingElement.getKind().equals(CLASS)) {
+            throw new ProcessingException(
+                    annotatedField,
+                    "Please apply " + getTypeAnnotationClass().getSimpleName() + " to fields of class: " + annotatedField.getSimpleName()
+            );
+        }
+
+        if (enclosingElement.getAnnotation(getTypeAnnotationClass()) == null) {
+            throw new ProcessingException(
+                    annotatedField,
+                    "Please annotate class " + enclosingElement.getSimpleName() + " with " + getTypeAnnotationClass().getSimpleName()
+            );
+        }
+
+        if (annotatedField.getModifiers().contains(PRIVATE)) {
+            throw new ProcessingException(
+                    annotatedField,
+                    getColumnAnnotationClass().getSimpleName() + " can not be applied to private field: " + annotatedField.getSimpleName()
+            );
+        }
+
+        if (annotatedField.getModifiers().contains(FINAL)) {
+            throw new ProcessingException(
+                    annotatedField,
+                    getColumnAnnotationClass().getSimpleName() + " can not be applied to final field: " + annotatedField.getSimpleName()
+            );
+        }
+    }
+
+    @Override
+    public synchronized void init(@NotNull final ProcessingEnvironment processingEnv) {
+        super.init(processingEnv);
+        filer = processingEnv.getFiler();
+        elementUtils = processingEnv.getElementUtils(); // why class name is "Elements" but method "getElementUtils()", OKAY..
+        messager = processingEnv.getMessager();
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latestSupported();
+    }
+
+    //endregion
+
+    /**
+     * For those who don't familiar with Annotation Processing API — this is the main method of Annotation Processor lifecycle
+     * <p>
+     * It will be called after Java Compiler will find lang elements annotated with annotations from {@link #getSupportedAnnotationTypes()}
+     *
+     * @param annotations set of annotations
+     * @param roundEnv    environment of current processing round
+     * @return true if annotation processor should not be invoked in next rounds of annotation processing, false otherwise
+     */
+    @Override
+    public boolean process(@Nullable final Set<? extends TypeElement> annotations, @NotNull final RoundEnvironment roundEnv) {
+        try {
+            final Map<TypeElement, TypeMeta> annotatedClasses = processAnnotatedClasses(roundEnv, elementUtils);
+
+            processAnnotatedFields(roundEnv, annotatedClasses);
+
+            validateAnnotatedClassesAndColumns(annotatedClasses);
+
+            final ResolverGenerator<TypeMeta> putResolverGenerator = createPutResolver();
+            final ResolverGenerator<TypeMeta> getResolverGenerator = createGetResolver();
+            final ResolverGenerator<TypeMeta> deleteResolverGenerator = createDeleteResolver();
+
+            for (TypeMeta typeMeta : annotatedClasses.values()) {
+                putResolverGenerator.generateJavaFile(typeMeta).writeTo(filer);
+                getResolverGenerator.generateJavaFile(typeMeta).writeTo(filer);
+                deleteResolverGenerator.generateJavaFile(typeMeta).writeTo(filer);
+            }
+        } catch (ProcessingException e) {
+            messager.printMessage(ERROR, e.getMessage(), e.element());
+        } catch (Exception e) {
+            messager.printMessage(ERROR, "Problem occurred with StorIOProcessor: " + e.getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Processes annotated class
+     *
+     * @param classElement type element
+     * @param elementUtils utils for working with elementUtils
+     * @return result of processing as {@link TypeMeta}
+     */
+    @NotNull
+    protected abstract TypeMeta processAnnotatedClass(@NotNull TypeElement classElement, @NotNull Elements elementUtils);
+
+    /**
+     * Processes fields
+     *
+     * @param roundEnvironment current processing environment
+     * @param annotatedClasses map of annotated classes
+     */
+    protected abstract void processAnnotatedFields(@NotNull final RoundEnvironment roundEnvironment, @NotNull Map<TypeElement, TypeMeta> annotatedClasses);
+
+    /**
+     * Processes annotated field and returns result of processing or throws exception
+     *
+     * @param annotatedField field that was annotated as column
+     * @return non-null {@link StorIOColumnMeta} with meta information about field
+     */
+    @NotNull
+    protected abstract ColumnMeta processAnnotatedField(@NotNull final Element annotatedField);
+
+    protected abstract void validateAnnotatedClassesAndColumns(@NotNull Map<TypeElement, TypeMeta> annotatedClasses);
+
+    @NotNull
+    protected abstract Class<? extends Annotation> getTypeAnnotationClass();
+
+    @NotNull
+    protected abstract Class<? extends Annotation> getColumnAnnotationClass();
+
+    @NotNull
+    protected abstract ResolverGenerator<TypeMeta> createPutResolver();
+
+    @NotNull
+    protected abstract ResolverGenerator<TypeMeta> createGetResolver();
+
+    @NotNull
+    protected abstract ResolverGenerator<TypeMeta> createDeleteResolver();
+}
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/Common.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/Common.java
new file mode 100644
index 0000000..2ba7c44
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/Common.java
@@ -0,0 +1,10 @@
+package com.pushtorefresh.storio.common.annotations.processor.generate;
+
+import com.squareup.javapoet.ClassName;
+
+public class Common {
+
+    public static final ClassName ANDROID_NON_NULL_ANNOTATION_CLASS_NAME = ClassName.get("android.support.annotation", "NonNull");
+
+    public static final String INDENT = "    "; // 4 spaces
+}
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/ResolverGenerator.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/ResolverGenerator.java
new file mode 100644
index 0000000..861d7c8
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/generate/ResolverGenerator.java
@@ -0,0 +1,12 @@
+package com.pushtorefresh.storio.common.annotations.processor.generate;
+
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOTypeMeta;
+import com.squareup.javapoet.JavaFile;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface ResolverGenerator <TypeMeta extends StorIOTypeMeta> {
+
+    @NotNull
+    JavaFile generateJavaFile(@NotNull TypeMeta typeMeta);
+}
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java
new file mode 100644
index 0000000..5c471e5
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java
@@ -0,0 +1,62 @@
+package com.pushtorefresh.storio.common.annotations.processor.introspection;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+public enum JavaType {
+
+    BOOLEAN,
+    BOOLEAN_OBJECT,
+    SHORT,
+    SHORT_OBJECT,
+    INTEGER,
+    INTEGER_OBJECT,
+    LONG,
+    LONG_OBJECT,
+    FLOAT,
+    FLOAT_OBJECT,
+    DOUBLE,
+    DOUBLE_OBJECT,
+    STRING,
+    BYTE_ARRAY;
+
+    @NotNull
+    public static JavaType from(@NotNull TypeMirror typeMirror) {
+        final TypeKind typeKind = typeMirror.getKind();
+        final String typeName = typeMirror.toString(); // fqn of type, for example java.lang.String
+
+        if (typeKind == TypeKind.BOOLEAN) {
+            return BOOLEAN;
+        } else if (Boolean.class.getCanonicalName().equals(typeName)) {
+            return BOOLEAN_OBJECT;
+        } else if (typeKind == TypeKind.SHORT) {
+            return SHORT;
+        } else if (Short.class.getCanonicalName().equals(typeName)) {
+            return SHORT_OBJECT;
+        } else if (typeKind == TypeKind.INT) {
+            return INTEGER;
+        } else if (Integer.class.getCanonicalName().equals(typeName)) {
+            return INTEGER_OBJECT;
+        } else if (typeKind == TypeKind.LONG) {
+            return LONG;
+        } else if (Long.class.getCanonicalName().equals(typeName)) {
+            return LONG_OBJECT;
+        } else if (typeKind == TypeKind.FLOAT) {
+            return FLOAT;
+        } else if (Float.class.getCanonicalName().equals(typeName)) {
+            return FLOAT_OBJECT;
+        } else if (typeKind == TypeKind.DOUBLE) {
+            return DOUBLE;
+        } else if (Double.class.getCanonicalName().equals(typeName)) {
+            return DOUBLE_OBJECT;
+        } else if (String.class.getCanonicalName().equals(typeName)) {
+            return STRING;
+        } else if (byte[].class.getCanonicalName().equals(typeName)) {
+            return BYTE_ARRAY;
+        } else {
+            throw new IllegalArgumentException("Unsupported type: " + typeMirror);
+        }
+    }
+}
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOColumnMeta.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOColumnMeta.java
new file mode 100644
index 0000000..5d52fe4
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOColumnMeta.java
@@ -0,0 +1,73 @@
+package com.pushtorefresh.storio.common.annotations.processor.introspection;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.annotation.Annotation;
+
+import javax.lang.model.element.Element;
+
+public class StorIOColumnMeta <ColumnAnnotation extends Annotation> {
+
+    @NotNull
+    public final Element enclosingElement;
+
+    @NotNull
+    public final Element element;
+
+    @NotNull
+    public final String fieldName;
+
+    @NotNull
+    public final JavaType javaType;
+
+    @NotNull
+    public final ColumnAnnotation storIOColumn;
+
+    public StorIOColumnMeta(
+            @NotNull Element enclosingElement,
+            @NotNull Element element,
+            @NotNull String fieldName,
+            @NotNull JavaType javaType, @NotNull ColumnAnnotation storIOColumn) {
+        this.enclosingElement = enclosingElement;
+        this.element = element;
+        this.fieldName = fieldName;
+        this.javaType = javaType;
+        this.storIOColumn = storIOColumn;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        StorIOColumnMeta<?> that = (StorIOColumnMeta<?>) o;
+
+        if (!enclosingElement.equals(that.enclosingElement)) return false;
+        if (!element.equals(that.element)) return false;
+        if (!fieldName.equals(that.fieldName)) return false;
+        if (javaType != that.javaType) return false;
+        return storIOColumn.equals(that.storIOColumn);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = enclosingElement.hashCode();
+        result = 31 * result + element.hashCode();
+        result = 31 * result + fieldName.hashCode();
+        result = 31 * result + javaType.hashCode();
+        result = 31 * result + storIOColumn.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "StorIOColumnMeta{" +
+                "enclosingElement=" + enclosingElement +
+                ", element=" + element +
+                ", fieldName='" + fieldName + '\'' +
+                ", javaType=" + javaType +
+                ", storIOColumn=" + storIOColumn +
+                '}';
+    }
+}
diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOTypeMeta.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOTypeMeta.java
new file mode 100644
index 0000000..1a871bd
--- /dev/null
+++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/StorIOTypeMeta.java
@@ -0,0 +1,67 @@
+package com.pushtorefresh.storio.common.annotations.processor.introspection;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+public class StorIOTypeMeta <TypeAnnotation extends Annotation, ColumnMeta extends StorIOColumnMeta> {
+
+    @NotNull
+    public final String simpleName;
+
+    @NotNull
+    public final String packageName;
+
+    @NotNull
+    public final TypeAnnotation storIOType;
+
+    /**
+     * Yep, this is MODIFIABLE Map, please use it carefully
+     */
+    @NotNull
+    public final Map<String, ColumnMeta> columns = new HashMap<String, ColumnMeta>();
+
+    public StorIOTypeMeta(
+            @NotNull String simpleName,
+            @NotNull String packageName,
+            @NotNull TypeAnnotation storIOType) {
+        this.simpleName = simpleName;
+        this.packageName = packageName;
+        this.storIOType = storIOType;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        StorIOTypeMeta<?, ?> that = (StorIOTypeMeta<?, ?>) o;
+
+        if (!simpleName.equals(that.simpleName)) return false;
+        if (!packageName.equals(that.packageName)) return false;
+        if (!storIOType.equals(that.storIOType)) return false;
+        return columns.equals(that.columns);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = simpleName.hashCode();
+        result = 31 * result + packageName.hashCode();
+        result = 31 * result + storIOType.hashCode();
+        result = 31 * result + columns.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "StorIOTypeMeta{" +
+                "simpleName='" + simpleName + '\'' +
+                ", packageName='" + packageName + '\'' +
+                ", storIOType=" + storIOType +
+                ", columns=" + columns +
+                '}';
+    }
+}
diff --git a/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java b/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java
new file mode 100644
index 0000000..7b27500
--- /dev/null
+++ b/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java
@@ -0,0 +1,126 @@
+package com.pushtorefresh.storio.common.annotations.processor.introspection;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BOOLEAN;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BYTE_ARRAY;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.DOUBLE;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.FLOAT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.INTEGER;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.LONG;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.LONG_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.SHORT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.SHORT_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.STRING;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class JavaTypeTest {
+
+    @NotNull
+    private static TypeMirror mockTypeMirror(@Nullable TypeKind typeKind, @Nullable String typeName) {
+        final TypeMirror typeMirror = mock(TypeMirror.class);
+
+        when(typeMirror.getKind())
+                .thenReturn(typeKind);
+
+        when(typeMirror.toString())
+                .thenReturn(typeName);
+
+        return typeMirror;
+    }
+
+    @Test
+    public void fromBoolean() {
+        final TypeMirror typeMirror = mockTypeMirror(TypeKind.BOOLEAN, null);
+        assertThat(JavaType.from(typeMirror)).isEqualTo(BOOLEAN);
+    }
+
+    @Test
+    public void fromBooleanObject() {
+        final TypeMirror typeMirror = mockTypeMirror(null, Boolean.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(BOOLEAN_OBJECT);
+    }
+
+    @Test
+    public void fromShort() {
+        final TypeMirror typeMirror = mockTypeMirror(TypeKind.SHORT, null);
+        assertThat(JavaType.from(typeMirror)).isEqualTo(SHORT);
+    }
+
+    @Test
+    public void fromShortObject() {
+        final TypeMirror typeMirror = mockTypeMirror(null, Short.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(SHORT_OBJECT);
+    }
+
+    @Test
+    public void fromInteger() {
+        final TypeMirror typeMirror = mockTypeMirror(TypeKind.INT, null);
+        assertThat(JavaType.from(typeMirror)).isEqualTo(INTEGER);
+    }
+
+    @Test
+    public void fromIntegerObject() {
+        final TypeMirror typeMirror = mockTypeMirror(null, Integer.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(INTEGER_OBJECT);
+    }
+
+    @Test
+    public void fromLong() {
+        final TypeMirror typeMirror = mockTypeMirror(TypeKind.LONG, null);
+        assertThat(JavaType.from(typeMirror)).isEqualTo(LONG);
+    }
+
+    @Test
+    public void fromLongObject() {
+        final TypeMirror typeMirror = mockTypeMirror(null, Long.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(LONG_OBJECT);
+    }
+
+    @Test
+    public void fromFloat() {
+        final TypeMirror typeMirror = mockTypeMirror(TypeKind.FLOAT, null);
+        assertThat(JavaType.from(typeMirror)).isEqualTo(FLOAT);
+    }
+
+    @Test
+    public void fromFloatObject() {
+        final TypeMirror typeMirror = mockTypeMirror(null, Float.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(FLOAT_OBJECT);
+    }
+
+    @Test
+    public void fromDouble() {
+        final TypeMirror typeMirror = mockTypeMirror(TypeKind.DOUBLE, null);
+        assertThat(JavaType.from(typeMirror)).isEqualTo(DOUBLE);
+    }
+
+    @Test
+    public void fromDoubleObject() {
+        final TypeMirror typeMirror = mockTypeMirror(null, Double.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(DOUBLE_OBJECT);
+    }
+
+    @Test
+    public void fromString() {
+        final TypeMirror typeMirror = mockTypeMirror(null, String.class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(STRING);
+    }
+
+    @Test
+    public void fromByteArray() {
+        final TypeMirror typeMirror = mockTypeMirror(null, byte[].class.getCanonicalName());
+        assertThat(JavaType.from(typeMirror)).isEqualTo(BYTE_ARRAY);
+    }
+}
diff --git a/storio-content-resolver-annotations-processor/build.gradle b/storio-content-resolver-annotations-processor/build.gradle
index 85e60db..9b7f8f6 100644
--- a/storio-content-resolver-annotations-processor/build.gradle
+++ b/storio-content-resolver-annotations-processor/build.gradle
@@ -5,6 +5,7 @@ sourceCompatibility = '1.6'
 
 dependencies {
     compile libraries.storIOContentResolverAnnotations
+    compile libraries.storIOCommonAnnotationsProcessor
     compile libraries.intellijAnnotations
     compile libraries.autoService
     compile libraries.javaPoet
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/ProcessingException.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/ProcessingException.java
deleted file mode 100644
index 340af06..0000000
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/ProcessingException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.pushtorefresh.storio.contentresolver.annotations.processor;
-
-import org.jetbrains.annotations.NotNull;
-
-import javax.lang.model.element.Element;
-
-/**
- * Useful for logging errors from AnnotationProcessor,
- * stores reference to {@link Element} that caused exception so IDE will show developer where is the problem
- */
-public class ProcessingException extends RuntimeException {
-
-    /**
-     * Element that caused exception
-     */
-    @NotNull
-    private final Element element;
-
-    public ProcessingException(@NotNull Element element, @NotNull String message) {
-        super(message);
-        this.element = element;
-    }
-
-    /**
-     * @return non-null element that caused exception
-     */
-    @NotNull
-    public Element element() {
-        return element;
-    }
-}
\ No newline at end of file
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/StorIOContentResolverProcessor.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/StorIOContentResolverProcessor.java
index 14d066f..0272059 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/StorIOContentResolverProcessor.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/StorIOContentResolverProcessor.java
@@ -1,29 +1,27 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor;
 
 import com.google.auto.service.AutoService;
+import com.pushtorefresh.storio.common.annotations.processor.ProcessingException;
+import com.pushtorefresh.storio.common.annotations.processor.StorIOAnnotationsProcessor;
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverColumn;
 import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverType;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.generate.DeleteResolverGenerator;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.generate.GetResolverGenerator;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.generate.PutResolverGenerator;
-import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.HashMap;
+import java.lang.annotation.Annotation;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Filer;
-import javax.annotation.processing.Messager;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.Processor;
 import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
@@ -31,7 +29,6 @@
 import static javax.lang.model.element.ElementKind.CLASS;
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PRIVATE;
-import static javax.tools.Diagnostic.Kind.ERROR;
 
 /**
  * Annotation processor for StorIOContentResolver
@@ -43,53 +40,17 @@
  */
 // Generate file with annotation processor declaration via another Annotation Processor!
 @AutoService(Processor.class)
-public class StorIOContentResolverProcessor extends AbstractProcessor {
+public class StorIOContentResolverProcessor extends StorIOAnnotationsProcessor<StorIOContentResolverTypeMeta, StorIOContentResolverColumnMeta> {
 
-    private Filer filer;
-    private Elements elementUtils;
-    private Messager messager;
-
-    /**
-     * Processes class annotations
-     *
-     * @param roundEnvironment environment
-     * @return non-null unmodifiable map(element, typeMeta)
-     */
-    private static Map<TypeElement, StorIOContentResolverTypeMeta> processAnnotatedClasses(@NotNull final RoundEnvironment roundEnvironment, @NotNull final Elements elementUtils) {
-        final Set<? extends Element> elementsAnnotatedWithStorIOContentResolverType
-                = roundEnvironment.getElementsAnnotatedWith(StorIOContentResolverType.class);
-
-        final Map<TypeElement, StorIOContentResolverTypeMeta> results
-                = new HashMap<TypeElement, StorIOContentResolverTypeMeta>(elementsAnnotatedWithStorIOContentResolverType.size());
-
-        for (final Element annotatedElement : elementsAnnotatedWithStorIOContentResolverType) {
-            final TypeElement classElement = validateAnnotatedClass(annotatedElement);
-            final StorIOContentResolverTypeMeta storIOContentResolverTypeMeta = processAnnotatedClass(classElement, elementUtils);
-            results.put(classElement, storIOContentResolverTypeMeta);
-        }
-
-        return results;
-    }
-
-    /**
-     * Checks that element annotated with {@link StorIOContentResolverType} satisfies all required conditions
-     *
-     * @param annotatedElement element annotated with {@link StorIOContentResolverType}
-     * @return {@link TypeElement} object
-     */
     @NotNull
-    private static TypeElement validateAnnotatedClass(@NotNull final Element annotatedElement) {
-        // we expect here that annotatedElement is Class, annotation requires that via @Target
-        TypeElement annotatedTypeElement = (TypeElement) annotatedElement;
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+        final Set<String> supportedAnnotations = new HashSet<String>(2);
 
-        if (annotatedTypeElement.getModifiers().contains(PRIVATE)) {
-            throw new ProcessingException(
-                    annotatedElement,
-                    StorIOContentResolverType.class.getSimpleName() + " can not be applied to private class: " + annotatedTypeElement.getQualifiedName()
-            );
-        }
+        supportedAnnotations.add(StorIOContentResolverType.class.getCanonicalName());
+        supportedAnnotations.add(StorIOContentResolverColumn.class.getCanonicalName());
 
-        return annotatedTypeElement;
+        return supportedAnnotations;
     }
 
     /**
@@ -100,7 +61,8 @@ private static TypeElement validateAnnotatedClass(@NotNull final Element annotat
      * @return result of processing as {@link StorIOContentResolverTypeMeta}
      */
     @NotNull
-    private static StorIOContentResolverTypeMeta processAnnotatedClass(@NotNull TypeElement classElement, @NotNull Elements elementUtils) {
+    @Override
+    protected StorIOContentResolverTypeMeta processAnnotatedClass(@NotNull TypeElement classElement, @NotNull Elements elementUtils) {
         final StorIOContentResolverType storIOContentResolverType = classElement.getAnnotation(StorIOContentResolverType.class);
 
         final String uri = storIOContentResolverType.uri();
@@ -124,7 +86,8 @@ private static StorIOContentResolverTypeMeta processAnnotatedClass(@NotNull Type
      * @param roundEnvironment current processing environment
      * @param annotatedClasses map of classes annotated with {@link StorIOContentResolverType}
      */
-    private void processAnnotatedFields(@NotNull final RoundEnvironment roundEnvironment, @NotNull final Map<TypeElement, StorIOContentResolverTypeMeta> annotatedClasses) {
+    @Override
+    protected void processAnnotatedFields(@NotNull final RoundEnvironment roundEnvironment, @NotNull final Map<TypeElement, StorIOContentResolverTypeMeta> annotatedClasses) {
         final Set<? extends Element> elementsAnnotatedWithStorIOContentResolverColumn
                 = roundEnvironment.getElementsAnnotatedWith(StorIOContentResolverColumn.class);
 
@@ -152,14 +115,13 @@ private void processAnnotatedFields(@NotNull final RoundEnvironment roundEnviron
         }
     }
 
-    //region Processing of annotated classes
-
     /**
      * Checks that element annotated with {@link StorIOContentResolverColumn} satisfies all required conditions
      *
      * @param annotatedField element annotated with {@link StorIOContentResolverColumn}
      */
-    private static void validateAnnotatedField(@NotNull final Element annotatedField) {
+    @Override
+    protected void validateAnnotatedField(@NotNull final Element annotatedField) {
         // we expect here that annotatedElement is Field, annotation requires that via @Target
 
         final Element enclosingElement = annotatedField.getEnclosingElement();
@@ -200,7 +162,8 @@ private static void validateAnnotatedField(@NotNull final Element annotatedField
      * @return non-null {@link StorIOContentResolverColumnMeta} with meta information about field
      */
     @NotNull
-    private static StorIOContentResolverColumnMeta processAnnotatedField(@NotNull final Element annotatedField) {
+    @Override
+    protected StorIOContentResolverColumnMeta processAnnotatedField(@NotNull final Element annotatedField) {
         final JavaType javaType;
 
         try {
@@ -232,7 +195,8 @@ private static StorIOContentResolverColumnMeta processAnnotatedField(@NotNull fi
         );
     }
 
-    private static void validateAnnotatedClassesAndColumns(@NotNull final Map<TypeElement, StorIOContentResolverTypeMeta> annotatedClasses) {
+    @Override
+    protected void validateAnnotatedClassesAndColumns(@NotNull final Map<TypeElement, StorIOContentResolverTypeMeta> annotatedClasses) {
         // check that each annotated class has columns with at least one key column
         for (Map.Entry<TypeElement, StorIOContentResolverTypeMeta> annotatedClass : annotatedClasses.entrySet()) {
             if (annotatedClass.getValue().columns.isEmpty()) {
@@ -247,7 +211,7 @@ private static void validateAnnotatedClassesAndColumns(@NotNull final Map<TypeEl
             boolean hasAtLeastOneKeyColumn = false;
 
             for (final StorIOContentResolverColumnMeta columnMeta : annotatedClass.getValue().columns.values()) {
-                if (columnMeta.storIOContentResolverColumn.key()) {
+                if (columnMeta.storIOColumn.key()) {
                     hasAtLeastOneKeyColumn = true;
                     break;
                 }
@@ -262,70 +226,34 @@ private static void validateAnnotatedClassesAndColumns(@NotNull final Map<TypeEl
             }
         }
     }
-    //endregion
-
-    //region Processing of annotated fields
 
+    @NotNull
     @Override
-    public synchronized void init(ProcessingEnvironment processingEnv) {
-        super.init(processingEnv);
-        filer = processingEnv.getFiler();
-        elementUtils = processingEnv.getElementUtils(); // why class name is "Elements" but method "getElementUtils()", OKAY..
-        messager = processingEnv.getMessager();
+    protected Class<? extends Annotation> getTypeAnnotationClass() {
+        return StorIOContentResolverType.class;
     }
 
+    @NotNull
     @Override
-    public Set<String> getSupportedAnnotationTypes() {
-        final Set<String> supportedAnnotations = new HashSet<String>(2);
-
-        supportedAnnotations.add(StorIOContentResolverType.class.getCanonicalName());
-        supportedAnnotations.add(StorIOContentResolverColumn.class.getCanonicalName());
-
-        return supportedAnnotations;
+    protected Class<? extends Annotation> getColumnAnnotationClass() {
+        return StorIOContentResolverColumn.class;
     }
 
+    @NotNull
     @Override
-    public SourceVersion getSupportedSourceVersion() {
-        return SourceVersion.latestSupported();
+    protected ResolverGenerator<StorIOContentResolverTypeMeta> createPutResolver() {
+        return new PutResolverGenerator();
     }
 
-    //endregion
-
-    /**
-     * For those who don't familiar with Annotation Processing API — this is the main method of Annotation Processor lifecycle
-     * <p>
-     * It will be called after Java Compiler will find lang elements annotated with annotations from {@link #getSupportedAnnotationTypes()}
-     *
-     * @param annotations set of annotations
-     * @param roundEnv    environment of current processing round
-     * @return true if annotation processor should not be invoked in next rounds of annotation processing, false otherwise
-     */
+    @NotNull
     @Override
-    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-        try {
-            final Map<TypeElement, StorIOContentResolverTypeMeta> annotatedClasses
-                    = processAnnotatedClasses(roundEnv, elementUtils);
-
-            processAnnotatedFields(roundEnv, annotatedClasses);
-
-            validateAnnotatedClassesAndColumns(annotatedClasses);
-
-            final PutResolverGenerator putResolverGenerator = new PutResolverGenerator();
-            final GetResolverGenerator getResolverGenerator = new GetResolverGenerator();
-            final DeleteResolverGenerator deleteResolverGenerator = new DeleteResolverGenerator();
-
-            for (final StorIOContentResolverTypeMeta storIOContentResolverTypeMeta : annotatedClasses.values()) {
-                putResolverGenerator.generateJavaFile(storIOContentResolverTypeMeta).writeTo(filer);
-                getResolverGenerator.generateJavaFile(storIOContentResolverTypeMeta).writeTo(filer);
-                deleteResolverGenerator.generateJavaFile(storIOContentResolverTypeMeta).writeTo(filer);
-            }
-
-        } catch (ProcessingException e) {
-            messager.printMessage(ERROR, e.getMessage(), e.element());
-        } catch (Exception e) {
-            messager.printMessage(ERROR, "Problem occurred with StorIOContentResolverProcessor: " + e.getMessage());
-        }
+    protected ResolverGenerator<StorIOContentResolverTypeMeta> createGetResolver() {
+        return new GetResolverGenerator();
+    }
 
-        return true;
+    @NotNull
+    @Override
+    protected ResolverGenerator<StorIOContentResolverTypeMeta> createDeleteResolver() {
+        return new DeleteResolverGenerator();
     }
 }
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/Common.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/Common.java
deleted file mode 100644
index 18882c7..0000000
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/Common.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.pushtorefresh.storio.contentresolver.annotations.processor.generate;
-
-import com.squareup.javapoet.ClassName;
-
-class Common {
-
-    static final ClassName ANDROID_NON_NULL_ANNOTATION_CLASS_NAME = ClassName.get("android.support.annotation", "NonNull");
-
-    static final String INDENT = "    "; // 4 spaces
-}
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/DeleteResolverGenerator.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/DeleteResolverGenerator.java
index 67f4a71..eada6d1 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/DeleteResolverGenerator.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/DeleteResolverGenerator.java
@@ -1,5 +1,6 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor.generate;
 
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
@@ -12,12 +13,12 @@
 
 import java.util.Map;
 
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.Common.INDENT;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.INDENT;
 import static javax.lang.model.element.Modifier.PROTECTED;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-public class DeleteResolverGenerator {
+public class DeleteResolverGenerator implements ResolverGenerator<StorIOContentResolverTypeMeta> {
 
     @NotNull
     public JavaFile generateJavaFile(@NotNull final StorIOContentResolverTypeMeta storIOContentResolverTypeMeta) {
@@ -54,7 +55,7 @@ private MethodSpec createMapToDeleteQueryMethodSpec(@NotNull final StorIOContent
                                 INDENT + ".where($S)\n" +
                                 INDENT + ".whereArgs($L)\n" +
                                 INDENT + ".build();\n",
-                        storIOContentResolverTypeMeta.storIOContentResolverType.uri(),
+                        storIOContentResolverTypeMeta.storIOType.uri(),
                         where.get(QueryGenerator.WHERE_CLAUSE),
                         where.get(QueryGenerator.WHERE_ARGS))
                 .build();
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java
index f597efe..66cd173 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java
@@ -1,7 +1,8 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor.generate;
 
-import com.pushtorefresh.storio.contentresolver.annotations.processor.ProcessingException;
-import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType;
+import com.pushtorefresh.storio.common.annotations.processor.ProcessingException;
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta;
 import com.squareup.javapoet.ClassName;
@@ -13,25 +14,25 @@
 
 import org.jetbrains.annotations.NotNull;
 
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.Common.INDENT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.BOOLEAN;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.BYTE_ARRAY;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.DOUBLE;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.FLOAT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.INTEGER;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.LONG;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.LONG_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.SHORT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.SHORT_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.STRING;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.INDENT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BOOLEAN;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BYTE_ARRAY;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.DOUBLE;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.FLOAT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.INTEGER;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.LONG;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.LONG_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.SHORT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.SHORT_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.STRING;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-public class GetResolverGenerator {
+public class GetResolverGenerator implements ResolverGenerator<StorIOContentResolverTypeMeta> {
 
     @NotNull
     public JavaFile generateJavaFile(@NotNull final StorIOContentResolverTypeMeta storIOContentResolverTypeMeta) {
@@ -65,7 +66,7 @@ private MethodSpec createMapFromCursorMethodSpec(@NotNull StorIOContentResolverT
                 .addCode("\n");
 
         for (final StorIOContentResolverColumnMeta columnMeta : storIOContentResolverTypeMeta.columns.values()) {
-            final String columnIndex = "cursor.getColumnIndex(\"" + columnMeta.storIOContentResolverColumn.name() + "\")";
+            final String columnIndex = "cursor.getColumnIndex(\"" + columnMeta.storIOColumn.name() + "\")";
 
             final String getFromCursor;
 
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGenerator.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGenerator.java
index 9929abb..32ce556 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGenerator.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGenerator.java
@@ -1,5 +1,6 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor.generate;
 
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta;
 import com.squareup.javapoet.ClassName;
@@ -13,12 +14,12 @@
 
 import java.util.Map;
 
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.Common.INDENT;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.INDENT;
 import static javax.lang.model.element.Modifier.PROTECTED;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-public class PutResolverGenerator {
+public class PutResolverGenerator  implements ResolverGenerator<StorIOContentResolverTypeMeta> {
 
     @NotNull
     public JavaFile generateJavaFile(@NotNull final StorIOContentResolverTypeMeta storIOContentResolverTypeMeta) {
@@ -54,7 +55,7 @@ private MethodSpec createMapToInsertQueryMethodSpec(@NotNull final StorIOContent
                 .addCode("return InsertQuery.builder()\n" +
                                 INDENT + ".uri($S)\n" +
                                 INDENT + ".build();\n",
-                        storIOContentResolverTypeMeta.storIOContentResolverType.uri())
+                        storIOContentResolverTypeMeta.storIOType.uri())
                 .build();
     }
 
@@ -76,7 +77,7 @@ private MethodSpec createMapToUpdateQueryMethodSpec(@NotNull final StorIOContent
                                 INDENT + ".where($S)\n" +
                                 INDENT + ".whereArgs($L)\n" +
                                 INDENT + ".build();\n",
-                        storIOContentResolverTypeMeta.storIOContentResolverType.uri(),
+                        storIOContentResolverTypeMeta.storIOType.uri(),
                         where.get(QueryGenerator.WHERE_CLAUSE),
                         where.get(QueryGenerator.WHERE_ARGS))
                 .build();
@@ -99,7 +100,7 @@ private MethodSpec createMapToContentValuesMethodSpec(@NotNull final StorIOConte
         for (final StorIOContentResolverColumnMeta columnMeta : storIOContentResolverTypeMeta.columns.values()) {
             builder.addStatement(
                     "contentValues.put($S, $L)",
-                    columnMeta.storIOContentResolverColumn.name(),
+                    columnMeta.storIOColumn.name(),
                     "object." + columnMeta.fieldName
             );
         }
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/QueryGenerator.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/QueryGenerator.java
index 6ce8c5a..6dfcd20 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/QueryGenerator.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/QueryGenerator.java
@@ -24,10 +24,10 @@
         int i = 0;
 
         for (final StorIOContentResolverColumnMeta columnMeta : storIOContentResolverTypeMeta.columns.values()) {
-            if (columnMeta.storIOContentResolverColumn.key()) {
+            if (columnMeta.storIOColumn.key()) {
                 if (i == 0) {
                     whereClause
-                            .append(columnMeta.storIOContentResolverColumn.name())
+                            .append(columnMeta.storIOColumn.name())
                             .append(" = ?");
 
                     whereArgs
@@ -37,7 +37,7 @@
                 } else {
                     whereClause
                             .append(" AND ")
-                            .append(columnMeta.storIOContentResolverColumn.name())
+                            .append(columnMeta.storIOColumn.name())
                             .append(" = ?");
 
                     whereArgs
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaType.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaType.java
deleted file mode 100644
index 46c1cd1..0000000
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaType.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.pushtorefresh.storio.contentresolver.annotations.processor.introspection;
-
-import org.jetbrains.annotations.NotNull;
-
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-
-public enum JavaType {
-
-    BOOLEAN,
-    BOOLEAN_OBJECT,
-    SHORT,
-    SHORT_OBJECT,
-    INTEGER,
-    INTEGER_OBJECT,
-    LONG,
-    LONG_OBJECT,
-    FLOAT,
-    FLOAT_OBJECT,
-    DOUBLE,
-    DOUBLE_OBJECT,
-    STRING,
-    BYTE_ARRAY;
-
-    @NotNull
-    public static JavaType from(@NotNull TypeMirror typeMirror) {
-        final TypeKind typeKind = typeMirror.getKind();
-        final String typeName = typeMirror.toString(); // fqn of type, for example java.lang.String
-
-        if (typeKind == TypeKind.BOOLEAN) {
-            return BOOLEAN;
-        } else if (Boolean.class.getCanonicalName().equals(typeName)) {
-            return BOOLEAN_OBJECT;
-        } else if (typeKind == TypeKind.SHORT) {
-            return SHORT;
-        } else if (Short.class.getCanonicalName().equals(typeName)) {
-            return SHORT_OBJECT;
-        } else if (typeKind == TypeKind.INT) {
-            return INTEGER;
-        } else if (Integer.class.getCanonicalName().equals(typeName)) {
-            return INTEGER_OBJECT;
-        } else if (typeKind == TypeKind.LONG) {
-            return LONG;
-        } else if (Long.class.getCanonicalName().equals(typeName)) {
-            return LONG_OBJECT;
-        } else if (typeKind == TypeKind.FLOAT) {
-            return FLOAT;
-        } else if (Float.class.getCanonicalName().equals(typeName)) {
-            return FLOAT_OBJECT;
-        } else if (typeKind == TypeKind.DOUBLE) {
-            return DOUBLE;
-        } else if (Double.class.getCanonicalName().equals(typeName)) {
-            return DOUBLE_OBJECT;
-        } else if (String.class.getCanonicalName().equals(typeName)) {
-            return STRING;
-        } else if (byte[].class.getCanonicalName().equals(typeName)) {
-            return BYTE_ARRAY;
-        } else {
-            throw new IllegalArgumentException("Unsupported type: " + typeMirror);
-        }
-    }
-}
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverColumnMeta.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverColumnMeta.java
index 8c5eda0..fcebd03 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverColumnMeta.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverColumnMeta.java
@@ -1,74 +1,21 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor.introspection;
 
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOColumnMeta;
 import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverColumn;
 
 import org.jetbrains.annotations.NotNull;
 
 import javax.lang.model.element.Element;
 
-public class StorIOContentResolverColumnMeta {
-
-    @NotNull
-    public final Element enclosingElement;
-
-    @NotNull
-    public final Element element;
-
-    @NotNull
-    public final String fieldName;
-
-    @NotNull
-    public final JavaType javaType;
-
-    @NotNull
-    public final StorIOContentResolverColumn storIOContentResolverColumn;
+public class StorIOContentResolverColumnMeta extends StorIOColumnMeta<StorIOContentResolverColumn> {
 
     public StorIOContentResolverColumnMeta(
             @NotNull Element enclosingElement,
             @NotNull Element element,
             @NotNull String fieldName,
             @NotNull JavaType javaType,
-            @NotNull StorIOContentResolverColumn storIOContentResolverColumn) {
-        this.enclosingElement = enclosingElement;
-        this.element = element;
-        this.fieldName = fieldName;
-        this.javaType = javaType;
-        this.storIOContentResolverColumn = storIOContentResolverColumn;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        StorIOContentResolverColumnMeta that = (StorIOContentResolverColumnMeta) o;
-
-        if (!enclosingElement.equals(that.enclosingElement)) return false;
-        if (!element.equals(that.element)) return false;
-        if (!fieldName.equals(that.fieldName)) return false;
-        if (javaType != that.javaType) return false;
-        return storIOContentResolverColumn.equals(that.storIOContentResolverColumn);
-
-    }
-
-    @Override
-    public int hashCode() {
-        int result = enclosingElement.hashCode();
-        result = 31 * result + element.hashCode();
-        result = 31 * result + fieldName.hashCode();
-        result = 31 * result + javaType.hashCode();
-        result = 31 * result + storIOContentResolverColumn.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "StorIOContentResolverColumnMeta{" +
-                "enclosingElement=" + enclosingElement +
-                ", element=" + element +
-                ", fieldName='" + fieldName + '\'' +
-                ", javaType=" + javaType +
-                ", storIOContentResolverColumn=" + storIOContentResolverColumn +
-                '}';
+            @NotNull StorIOContentResolverColumn storIOColumn) {
+        super(enclosingElement, element, fieldName, javaType, storIOColumn);
     }
 }
diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverTypeMeta.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverTypeMeta.java
index d22f33a..55fc7a2 100644
--- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverTypeMeta.java
+++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/StorIOContentResolverTypeMeta.java
@@ -1,68 +1,16 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor.introspection;
 
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOTypeMeta;
 import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverType;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.HashMap;
-import java.util.Map;
-
-public class StorIOContentResolverTypeMeta {
-
-    @NotNull
-    public final String simpleName;
-
-    @NotNull
-    public final String packageName;
-
-    @NotNull
-    public final StorIOContentResolverType storIOContentResolverType;
-
-    /**
-     * Yep, this is MODIFIABLE Map, please use it carefully
-     */
-    @NotNull
-    public final Map<String, StorIOContentResolverColumnMeta> columns = new HashMap<String, StorIOContentResolverColumnMeta>();
+public class StorIOContentResolverTypeMeta extends StorIOTypeMeta<StorIOContentResolverType, StorIOContentResolverColumnMeta> {
 
     public StorIOContentResolverTypeMeta(
             @NotNull String simpleName,
             @NotNull String packageName,
-            @NotNull StorIOContentResolverType storIOContentResolverType) {
-        this.simpleName = simpleName;
-        this.packageName = packageName;
-        this.storIOContentResolverType = storIOContentResolverType;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        StorIOContentResolverTypeMeta that = (StorIOContentResolverTypeMeta) o;
-
-        if (!simpleName.equals(that.simpleName)) return false;
-        if (!packageName.equals(that.packageName)) return false;
-        if (!storIOContentResolverType.equals(that.storIOContentResolverType)) return false;
-        return columns.equals(that.columns);
-
-    }
-
-    @Override
-    public int hashCode() {
-        int result = simpleName.hashCode();
-        result = 31 * result + packageName.hashCode();
-        result = 31 * result + storIOContentResolverType.hashCode();
-        result = 31 * result + columns.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "StorIOContentResolverTypeMeta{" +
-                "simpleName='" + simpleName + '\'' +
-                ", packageName='" + packageName + '\'' +
-                ", storIOContentResolverType=" + storIOContentResolverType +
-                ", columns=" + columns +
-                '}';
+            @NotNull StorIOContentResolverType storIOType) {
+        super(simpleName, packageName, storIOType);
     }
 }
diff --git a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java
index 5eba5a8..e700bfb 100644
--- a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java
+++ b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java
@@ -1,8 +1,8 @@
 package com.pushtorefresh.storio.contentresolver.annotations.processor.generate;
 
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverColumn;
 import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverType;
-import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta;
 import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta;
 import com.squareup.javapoet.JavaFile;
diff --git a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaTypeTest.java b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaTypeTest.java
deleted file mode 100644
index fea2ed2..0000000
--- a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/introspection/JavaTypeTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.pushtorefresh.storio.contentresolver.annotations.processor.introspection;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.junit.Test;
-
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.BOOLEAN;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.BYTE_ARRAY;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.DOUBLE;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.FLOAT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.INTEGER;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.LONG;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.LONG_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.SHORT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.SHORT_OBJECT;
-import static com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.JavaType.STRING;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class JavaTypeTest {
-
-    @NotNull
-    private static TypeMirror mockTypeMirror(@Nullable TypeKind typeKind, @Nullable String typeName) {
-        final TypeMirror typeMirror = mock(TypeMirror.class);
-
-        when(typeMirror.getKind())
-                .thenReturn(typeKind);
-
-        when(typeMirror.toString())
-                .thenReturn(typeName);
-
-        return typeMirror;
-    }
-
-    @Test
-    public void fromBoolean() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.BOOLEAN, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(BOOLEAN);
-    }
-
-    @Test
-    public void fromBooleanObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Boolean.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(BOOLEAN_OBJECT);
-    }
-
-    @Test
-    public void fromShort() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.SHORT, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(SHORT);
-    }
-
-    @Test
-    public void fromShortObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Short.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(SHORT_OBJECT);
-    }
-
-    @Test
-    public void fromInteger() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.INT, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(INTEGER);
-    }
-
-    @Test
-    public void fromIntegerObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Integer.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(INTEGER_OBJECT);
-    }
-
-    @Test
-    public void fromLong() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.LONG, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(LONG);
-    }
-
-    @Test
-    public void fromLongObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Long.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(LONG_OBJECT);
-    }
-
-    @Test
-    public void fromFloat() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.FLOAT, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(FLOAT);
-    }
-
-    @Test
-    public void fromFloatObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Float.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(FLOAT_OBJECT);
-    }
-
-    @Test
-    public void fromDouble() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.DOUBLE, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(DOUBLE);
-    }
-
-    @Test
-    public void fromDoubleObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Double.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(DOUBLE_OBJECT);
-    }
-
-    @Test
-    public void fromString() {
-        final TypeMirror typeMirror = mockTypeMirror(null, String.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(STRING);
-    }
-
-    @Test
-    public void fromByteArray() {
-        final TypeMirror typeMirror = mockTypeMirror(null, byte[].class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(BYTE_ARRAY);
-    }
-}
diff --git a/storio-sqlite-annotations-processor/build.gradle b/storio-sqlite-annotations-processor/build.gradle
index 93ee1b0..99fcf56 100644
--- a/storio-sqlite-annotations-processor/build.gradle
+++ b/storio-sqlite-annotations-processor/build.gradle
@@ -5,6 +5,7 @@ sourceCompatibility = '1.6'
 
 dependencies {
     compile libraries.storIOSQLiteAnnotations
+    compile libraries.storIOCommonAnnotationsProcessor
     compile libraries.intellijAnnotations
     compile libraries.autoService
     compile libraries.javaPoet
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/ProcessingException.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/ProcessingException.java
deleted file mode 100644
index 2cb2565..0000000
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/ProcessingException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.pushtorefresh.storio.sqlite.annotations.processor;
-
-import org.jetbrains.annotations.NotNull;
-
-import javax.lang.model.element.Element;
-
-/**
- * Useful for logging errors from AnnotationProcessor,
- * stores reference to {@link Element} that caused exception so IDE will show developer where is the problem
- */
-public class ProcessingException extends RuntimeException {
-
-    /**
-     * Element that caused exception
-     */
-    @NotNull
-    private final Element element;
-
-    public ProcessingException(@NotNull Element element, @NotNull String message) {
-        super(message);
-        this.element = element;
-    }
-
-    /**
-     * @return non-null element that caused exception
-     */
-    @NotNull
-    public Element element() {
-        return element;
-    }
-}
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/StorIOSQLiteProcessor.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/StorIOSQLiteProcessor.java
index d165849..746a0f9 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/StorIOSQLiteProcessor.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/StorIOSQLiteProcessor.java
@@ -1,39 +1,31 @@
 package com.pushtorefresh.storio.sqlite.annotations.processor;
 
 import com.google.auto.service.AutoService;
+import com.pushtorefresh.storio.common.annotations.processor.ProcessingException;
+import com.pushtorefresh.storio.common.annotations.processor.StorIOAnnotationsProcessor;
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
 import com.pushtorefresh.storio.sqlite.annotations.processor.generate.DeleteResolverGenerator;
 import com.pushtorefresh.storio.sqlite.annotations.processor.generate.GetResolverGenerator;
 import com.pushtorefresh.storio.sqlite.annotations.processor.generate.PutResolverGenerator;
-import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collections;
-import java.util.HashMap;
+import java.lang.annotation.Annotation;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Filer;
-import javax.annotation.processing.Messager;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.Processor;
 import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
 
-import static javax.lang.model.element.ElementKind.CLASS;
-import static javax.lang.model.element.Modifier.FINAL;
-import static javax.lang.model.element.Modifier.PRIVATE;
-import static javax.tools.Diagnostic.Kind.ERROR;
-
 /**
  * Annotation processor for StorIOSQLite
  * <p>
@@ -44,54 +36,17 @@
  */
 // Generate file with annotation processor declaration via another Annotation Processor!
 @AutoService(Processor.class)
-public class StorIOSQLiteProcessor extends AbstractProcessor {
-
-    private Filer filer;
-    private Elements elementUtils;
-    private Messager messager;
-
-    /**
-     * Processes class annotations
-     *
-     * @param roundEnvironment environment
-     * @return non-null unmodifiable map(element, typeMeta)
-     */
-    @NotNull
-    private static Map<TypeElement, StorIOSQLiteTypeMeta> processAnnotatedClasses(@NotNull final RoundEnvironment roundEnvironment, @NotNull final Elements elementUtils) {
-        final Set<? extends Element> elementsAnnotatedWithStorIOSQLiteType
-                = roundEnvironment.getElementsAnnotatedWith(StorIOSQLiteType.class);
-
-        final Map<TypeElement, StorIOSQLiteTypeMeta> results
-                = new HashMap<TypeElement, StorIOSQLiteTypeMeta>(elementsAnnotatedWithStorIOSQLiteType.size());
-
-        for (final Element annotatedElement : elementsAnnotatedWithStorIOSQLiteType) {
-            final TypeElement classElement = validateAnnotatedClass(annotatedElement);
-            final StorIOSQLiteTypeMeta storIOSQLiteTypeMeta = processAnnotatedClass(classElement, elementUtils);
-            results.put(classElement, storIOSQLiteTypeMeta);
-        }
+public class StorIOSQLiteProcessor extends StorIOAnnotationsProcessor<StorIOSQLiteTypeMeta, StorIOSQLiteColumnMeta> {
 
-        return Collections.unmodifiableMap(results);
-    }
-
-    /**
-     * Checks that element annotated with {@link StorIOSQLiteType} satisfies all required conditions
-     *
-     * @param annotatedElement element annotated with {@link StorIOSQLiteType}
-     * @return {@link TypeElement} object
-     */
     @NotNull
-    private static TypeElement validateAnnotatedClass(@NotNull final Element annotatedElement) {
-        // we expect here that annotatedElement is Class, annotation requires that via @Target
-        final TypeElement annotatedTypeElement = (TypeElement) annotatedElement;
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+        final Set<String> supportedAnnotations = new HashSet<String>(2);
 
-        if (annotatedTypeElement.getModifiers().contains(PRIVATE)) {
-            throw new ProcessingException(
-                    annotatedElement,
-                    StorIOSQLiteType.class.getSimpleName() + " can not be applied to private class: " + annotatedTypeElement.getQualifiedName()
-            );
-        }
+        supportedAnnotations.add(StorIOSQLiteType.class.getCanonicalName());
+        supportedAnnotations.add(StorIOSQLiteColumn.class.getCanonicalName());
 
-        return annotatedTypeElement;
+        return supportedAnnotations;
     }
 
     /**
@@ -102,7 +57,8 @@ private static TypeElement validateAnnotatedClass(@NotNull final Element annotat
      * @return result of processing as {@link StorIOSQLiteTypeMeta}
      */
     @NotNull
-    private static StorIOSQLiteTypeMeta processAnnotatedClass(@NotNull TypeElement classElement, @NotNull Elements elementUtils) {
+    @Override
+    protected StorIOSQLiteTypeMeta processAnnotatedClass(@NotNull TypeElement classElement, @NotNull Elements elementUtils) {
         final StorIOSQLiteType storIOSQLiteType = classElement.getAnnotation(StorIOSQLiteType.class);
 
         final String tableName = storIOSQLiteType.table();
@@ -126,7 +82,8 @@ private static StorIOSQLiteTypeMeta processAnnotatedClass(@NotNull TypeElement c
      * @param roundEnvironment current processing environment
      * @param annotatedClasses map of classes annotated with {@link StorIOSQLiteType}
      */
-    private static void processAnnotatedFields(@NotNull final RoundEnvironment roundEnvironment, @NotNull Map<TypeElement, StorIOSQLiteTypeMeta> annotatedClasses) {
+    @Override
+    protected void processAnnotatedFields(@NotNull final RoundEnvironment roundEnvironment, @NotNull Map<TypeElement, StorIOSQLiteTypeMeta> annotatedClasses) {
         final Set<? extends Element> elementsAnnotatedWithStorIOSQLiteColumn
                 = roundEnvironment.getElementsAnnotatedWith(StorIOSQLiteColumn.class);
 
@@ -147,53 +104,12 @@ private static void processAnnotatedFields(@NotNull final RoundEnvironment round
 
             // Put meta column info
             // If class already contains column with same name -> throw exception
-            if (storIOSQLiteTypeMeta.columns.put(storIOSQLiteColumnMeta.storIOSQLiteColumn.name(), storIOSQLiteColumnMeta) != null) {
+            if (storIOSQLiteTypeMeta.columns.put(storIOSQLiteColumnMeta.storIOColumn.name(), storIOSQLiteColumnMeta) != null) {
                 throw new ProcessingException(annotatedFieldElement, "Column name already used in this class");
             }
         }
     }
 
-    //region Processing of annotated classes
-
-    /**
-     * Checks that element annotated with {@link StorIOSQLiteColumn} satisfies all required conditions
-     *
-     * @param annotatedField element annotated with {@link StorIOSQLiteColumn}
-     */
-    private static void validateAnnotatedField(@NotNull final Element annotatedField) {
-        // we expect here that annotatedElement is Field, annotation requires that via @Target
-
-        final Element enclosingElement = annotatedField.getEnclosingElement();
-
-        if (!enclosingElement.getKind().equals(CLASS)) {
-            throw new ProcessingException(
-                    annotatedField,
-                    "Please apply " + StorIOSQLiteType.class.getSimpleName() + " to fields of class: " + annotatedField.getSimpleName()
-            );
-        }
-
-        if (enclosingElement.getAnnotation(StorIOSQLiteType.class) == null) {
-            throw new ProcessingException(
-                    annotatedField,
-                    "Please annotate class " + enclosingElement.getSimpleName() + " with " + StorIOSQLiteType.class.getSimpleName()
-            );
-        }
-
-        if (annotatedField.getModifiers().contains(PRIVATE)) {
-            throw new ProcessingException(
-                    annotatedField,
-                    StorIOSQLiteColumn.class.getSimpleName() + " can not be applied to private field: " + annotatedField.getSimpleName()
-            );
-        }
-
-        if (annotatedField.getModifiers().contains(FINAL)) {
-            throw new ProcessingException(
-                    annotatedField,
-                    StorIOSQLiteColumn.class.getSimpleName() + " can not be applied to final field: " + annotatedField.getSimpleName()
-            );
-        }
-    }
-
     /**
      * Processes annotated field and returns result of processing or throws exception
      *
@@ -201,7 +117,8 @@ private static void validateAnnotatedField(@NotNull final Element annotatedField
      * @return non-null {@link StorIOSQLiteColumnMeta} with meta information about field
      */
     @NotNull
-    private static StorIOSQLiteColumnMeta processAnnotatedField(@NotNull final Element annotatedField) {
+    @Override
+    protected StorIOSQLiteColumnMeta processAnnotatedField(@NotNull final Element annotatedField) {
         final JavaType javaType;
 
         try {
@@ -232,7 +149,8 @@ private static StorIOSQLiteColumnMeta processAnnotatedField(@NotNull final Eleme
         );
     }
 
-    private static void validateAnnotatedClassesAndColumns(@NotNull Map<TypeElement, StorIOSQLiteTypeMeta> annotatedClasses) {
+    @Override
+    protected void validateAnnotatedClassesAndColumns(@NotNull Map<TypeElement, StorIOSQLiteTypeMeta> annotatedClasses) {
         // check that each annotated class has columns with at least one key column
         for (final Map.Entry<TypeElement, StorIOSQLiteTypeMeta> annotatedClass : annotatedClasses.entrySet()) {
             if (annotatedClass.getValue().columns.size() == 0) {
@@ -247,7 +165,7 @@ private static void validateAnnotatedClassesAndColumns(@NotNull Map<TypeElement,
             boolean hasAtLeastOneKeyColumn = false;
 
             for (final StorIOSQLiteColumnMeta columnMeta : annotatedClass.getValue().columns.values()) {
-                if (columnMeta.storIOSQLiteColumn.key()) {
+                if (columnMeta.storIOColumn.key()) {
                     hasAtLeastOneKeyColumn = true;
                     break;
                 }
@@ -263,69 +181,33 @@ private static void validateAnnotatedClassesAndColumns(@NotNull Map<TypeElement,
         }
     }
 
-    //endregion
-
-    //region Processing of annotated fields
-
+    @NotNull
     @Override
-    public synchronized void init(ProcessingEnvironment processingEnv) {
-        super.init(processingEnv);
-        filer = processingEnv.getFiler();
-        elementUtils = processingEnv.getElementUtils(); // why class name is "Elements" but method "getElementUtils()", OKAY..
-        messager = processingEnv.getMessager();
+    protected Class<? extends Annotation> getTypeAnnotationClass() {
+        return StorIOSQLiteType.class;
     }
 
+    @NotNull
     @Override
-    public Set<String> getSupportedAnnotationTypes() {
-        final Set<String> supportedAnnotations = new HashSet<String>(2);
-
-        supportedAnnotations.add(StorIOSQLiteType.class.getCanonicalName());
-        supportedAnnotations.add(StorIOSQLiteColumn.class.getCanonicalName());
-
-        return supportedAnnotations;
+    protected Class<? extends Annotation> getColumnAnnotationClass() {
+        return StorIOSQLiteColumn.class;
     }
 
+    @NotNull
     @Override
-    public SourceVersion getSupportedSourceVersion() {
-        return SourceVersion.latestSupported();
+    protected ResolverGenerator<StorIOSQLiteTypeMeta> createPutResolver() {
+        return new PutResolverGenerator();
     }
 
-    //endregion
-
-    /**
-     * For those who don't familiar with Annotation Processing API — this is the main method of Annotation Processor lifecycle
-     * <p>
-     * It will be called after Java Compiler will find lang elements annotated with annotations from {@link #getSupportedAnnotationTypes()}
-     *
-     * @param annotations set of annotations
-     * @param roundEnv    environment of current processing round
-     * @return true if annotation processor should not be invoked in next rounds of annotation processing, false otherwise
-     */
+    @NotNull
     @Override
-    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
-        try {
-            final Map<TypeElement, StorIOSQLiteTypeMeta> annotatedClasses
-                    = processAnnotatedClasses(roundEnv, elementUtils);
-
-            processAnnotatedFields(roundEnv, annotatedClasses);
-
-            validateAnnotatedClassesAndColumns(annotatedClasses);
-
-            final PutResolverGenerator putResolverGenerator = new PutResolverGenerator();
-            final GetResolverGenerator getResolverGenerator = new GetResolverGenerator();
-            final DeleteResolverGenerator deleteResolverGenerator = new DeleteResolverGenerator();
-
-            for (StorIOSQLiteTypeMeta storIOSQLiteTypeMeta : annotatedClasses.values()) {
-                putResolverGenerator.generateJavaFile(storIOSQLiteTypeMeta).writeTo(filer);
-                getResolverGenerator.generateJavaFile(storIOSQLiteTypeMeta).writeTo(filer);
-                deleteResolverGenerator.generateJavaFile(storIOSQLiteTypeMeta).writeTo(filer);
-            }
-        } catch (ProcessingException e) {
-            messager.printMessage(ERROR, e.getMessage(), e.element());
-        } catch (Exception e) {
-            messager.printMessage(ERROR, "Problem occurred with StorIOSQLiteProcessor: " + e.getMessage());
-        }
+    protected ResolverGenerator<StorIOSQLiteTypeMeta> createGetResolver() {
+        return new GetResolverGenerator();
+    }
 
-        return true;
+    @NotNull
+    @Override
+    protected ResolverGenerator<StorIOSQLiteTypeMeta> createDeleteResolver() {
+        return new DeleteResolverGenerator();
     }
 }
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/Common.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/Common.java
deleted file mode 100644
index 27970dd..0000000
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/Common.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.pushtorefresh.storio.sqlite.annotations.processor.generate;
-
-import com.squareup.javapoet.ClassName;
-
-class Common {
-
-    static final ClassName ANDROID_NON_NULL_ANNOTATION_CLASS_NAME = ClassName.get("android.support.annotation", "NonNull");
-
-    static final String INDENT = "    "; // 4 spaces
-}
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/DeleteResolverGenerator.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/DeleteResolverGenerator.java
index 0c8679f..9640986 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/DeleteResolverGenerator.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/DeleteResolverGenerator.java
@@ -1,5 +1,6 @@
 package com.pushtorefresh.storio.sqlite.annotations.processor.generate;
 
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
@@ -12,12 +13,12 @@
 
 import java.util.Map;
 
-import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.Common.INDENT;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.INDENT;
 import static javax.lang.model.element.Modifier.PROTECTED;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-public class DeleteResolverGenerator {
+public class DeleteResolverGenerator implements ResolverGenerator<StorIOSQLiteTypeMeta> {
 
     @NotNull
     public JavaFile generateJavaFile(@NotNull StorIOSQLiteTypeMeta storIOSQLiteTypeMeta) {
@@ -54,7 +55,7 @@ private MethodSpec createMapToDeleteQueryMethodSpec(@NotNull StorIOSQLiteTypeMet
                                 INDENT + ".where($S)\n" +
                                 INDENT + ".whereArgs($L)\n" +
                                 INDENT + ".build();\n",
-                        storIOSQLiteTypeMeta.storIOSQLiteType.table(),
+                        storIOSQLiteTypeMeta.storIOType.table(),
                         where.get(QueryGenerator.WHERE_CLAUSE),
                         where.get(QueryGenerator.WHERE_ARGS))
                 .build();
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java
index 808d850..2da3be7 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java
@@ -1,7 +1,8 @@
 package com.pushtorefresh.storio.sqlite.annotations.processor.generate;
 
-import com.pushtorefresh.storio.sqlite.annotations.processor.ProcessingException;
-import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType;
+import com.pushtorefresh.storio.common.annotations.processor.ProcessingException;
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta;
 import com.squareup.javapoet.ClassName;
@@ -13,25 +14,25 @@
 
 import org.jetbrains.annotations.NotNull;
 
-import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.Common.INDENT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.BOOLEAN;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.BYTE_ARRAY;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.DOUBLE;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.FLOAT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.INTEGER;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.LONG;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.LONG_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.SHORT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.SHORT_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.STRING;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.INDENT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BOOLEAN;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.BYTE_ARRAY;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.DOUBLE;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.FLOAT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.INTEGER;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.LONG;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.LONG_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.SHORT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.SHORT_OBJECT;
+import static com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType.STRING;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-public class GetResolverGenerator {
+public class GetResolverGenerator implements ResolverGenerator<StorIOSQLiteTypeMeta> {
 
     @NotNull
     public JavaFile generateJavaFile(@NotNull StorIOSQLiteTypeMeta storIOSQLiteTypeMeta) {
@@ -65,7 +66,7 @@ private MethodSpec createMapFromCursorMethodSpec(@NotNull StorIOSQLiteTypeMeta s
                 .addCode("\n");
 
         for (final StorIOSQLiteColumnMeta columnMeta : storIOSQLiteTypeMeta.columns.values()) {
-            final String columnIndex = "cursor.getColumnIndex(\"" + columnMeta.storIOSQLiteColumn.name() + "\")";
+            final String columnIndex = "cursor.getColumnIndex(\"" + columnMeta.storIOColumn.name() + "\")";
 
             final String getFromCursor;
 
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGenerator.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGenerator.java
index d10269a..f4bb604 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGenerator.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGenerator.java
@@ -1,5 +1,6 @@
 package com.pushtorefresh.storio.sqlite.annotations.processor.generate;
 
+import com.pushtorefresh.storio.common.annotations.processor.generate.ResolverGenerator;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta;
 import com.squareup.javapoet.ClassName;
@@ -13,12 +14,12 @@
 
 import java.util.Map;
 
-import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.Common.INDENT;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.ANDROID_NON_NULL_ANNOTATION_CLASS_NAME;
+import static com.pushtorefresh.storio.common.annotations.processor.generate.Common.INDENT;
 import static javax.lang.model.element.Modifier.PROTECTED;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-public class PutResolverGenerator {
+public class PutResolverGenerator implements ResolverGenerator<StorIOSQLiteTypeMeta> {
 
     @NotNull
     public JavaFile generateJavaFile(@NotNull StorIOSQLiteTypeMeta storIOSQLiteTypeMeta) {
@@ -53,7 +54,7 @@ private MethodSpec createMapToInsertQueryMethodSpec(@NotNull StorIOSQLiteTypeMet
                 .addCode("return InsertQuery.builder()\n" +
                                 INDENT + ".table($S)\n" +
                                 INDENT + ".build();\n",
-                        storIOSQLiteTypeMeta.storIOSQLiteType.table())
+                        storIOSQLiteTypeMeta.storIOType.table())
                 .build();
     }
 
@@ -75,7 +76,7 @@ private MethodSpec createMapToUpdateQueryMethodSpec(@NotNull StorIOSQLiteTypeMet
                                 INDENT + ".where($S)\n" +
                                 INDENT + ".whereArgs($L)\n" +
                                 INDENT + ".build();\n",
-                        storIOSQLiteTypeMeta.storIOSQLiteType.table(),
+                        storIOSQLiteTypeMeta.storIOType.table(),
                         where.get(QueryGenerator.WHERE_CLAUSE),
                         where.get(QueryGenerator.WHERE_ARGS))
                 .build();
@@ -98,7 +99,7 @@ private MethodSpec createMapToContentValuesMethodSpec(@NotNull StorIOSQLiteTypeM
         for (StorIOSQLiteColumnMeta columnMeta : storIOSQLiteTypeMeta.columns.values()) {
             builder.addStatement(
                     "contentValues.put($S, $L)",
-                    columnMeta.storIOSQLiteColumn.name(),
+                    columnMeta.storIOColumn.name(),
                     "object." + columnMeta.fieldName
             );
         }
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/QueryGenerator.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/QueryGenerator.java
index 7445d85..1c98044 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/QueryGenerator.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/QueryGenerator.java
@@ -22,10 +22,10 @@
         int i = 0;
 
         for (final StorIOSQLiteColumnMeta columnMeta : storIOSQLiteTypeMeta.columns.values()) {
-            if (columnMeta.storIOSQLiteColumn.key()) {
+            if (columnMeta.storIOColumn.key()) {
                 if (i == 0) {
                     whereClause
-                            .append(columnMeta.storIOSQLiteColumn.name())
+                            .append(columnMeta.storIOColumn.name())
                             .append(" = ?");
 
                     whereArgs
@@ -35,7 +35,7 @@
                 } else {
                     whereClause
                             .append(" AND ")
-                            .append(columnMeta.storIOSQLiteColumn.name())
+                            .append(columnMeta.storIOColumn.name())
                             .append(" = ?");
 
                     whereArgs
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaType.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaType.java
deleted file mode 100644
index 48c9cf4..0000000
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaType.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.pushtorefresh.storio.sqlite.annotations.processor.introspection;
-
-import org.jetbrains.annotations.NotNull;
-
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-
-public enum JavaType {
-
-    BOOLEAN,
-    BOOLEAN_OBJECT,
-    SHORT,
-    SHORT_OBJECT,
-    INTEGER,
-    INTEGER_OBJECT,
-    LONG,
-    LONG_OBJECT,
-    FLOAT,
-    FLOAT_OBJECT,
-    DOUBLE,
-    DOUBLE_OBJECT,
-    STRING,
-    BYTE_ARRAY;
-
-    @NotNull
-    public static JavaType from(@NotNull TypeMirror typeMirror) {
-        final TypeKind typeKind = typeMirror.getKind();
-        final String typeName = typeMirror.toString(); // fqn of type, for example java.lang.String
-
-        if (typeKind == TypeKind.BOOLEAN) {
-            return BOOLEAN;
-        } else if (Boolean.class.getCanonicalName().equals(typeName)) {
-            return BOOLEAN_OBJECT;
-        } else if (typeKind == TypeKind.SHORT) {
-            return SHORT;
-        } else if (Short.class.getCanonicalName().equals(typeName)) {
-            return SHORT_OBJECT;
-        } else if (typeKind == TypeKind.INT) {
-            return INTEGER;
-        } else if (Integer.class.getCanonicalName().equals(typeName)) {
-            return INTEGER_OBJECT;
-        } else if (typeKind == TypeKind.LONG) {
-            return LONG;
-        } else if (Long.class.getCanonicalName().equals(typeName)) {
-            return LONG_OBJECT;
-        } else if (typeKind == TypeKind.FLOAT) {
-            return FLOAT;
-        } else if (Float.class.getCanonicalName().equals(typeName)) {
-            return FLOAT_OBJECT;
-        } else if (typeKind == TypeKind.DOUBLE) {
-            return DOUBLE;
-        } else if (Double.class.getCanonicalName().equals(typeName)) {
-            return DOUBLE_OBJECT;
-        } else if (String.class.getCanonicalName().equals(typeName)) {
-            return STRING;
-        } else if (byte[].class.getCanonicalName().equals(typeName)) {
-            return BYTE_ARRAY;
-        } else {
-            throw new IllegalArgumentException("Unsupported type: " + typeMirror);
-        }
-    }
-}
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteColumnMeta.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteColumnMeta.java
index 9513e73..604feb5 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteColumnMeta.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteColumnMeta.java
@@ -1,68 +1,21 @@
 package com.pushtorefresh.storio.sqlite.annotations.processor.introspection;
 
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOColumnMeta;
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
 
 import org.jetbrains.annotations.NotNull;
 
 import javax.lang.model.element.Element;
 
-public class StorIOSQLiteColumnMeta {
+public class StorIOSQLiteColumnMeta extends StorIOColumnMeta<StorIOSQLiteColumn> {
 
-    @NotNull
-    public final Element enclosingElement;
-
-    @NotNull
-    public final Element element;
-
-    @NotNull
-    public final String fieldName;
-
-    @NotNull
-    public final JavaType javaType;
-
-    @NotNull
-    public final StorIOSQLiteColumn storIOSQLiteColumn;
-
-    public StorIOSQLiteColumnMeta(@NotNull Element enclosingElement, @NotNull Element element, @NotNull String fieldName, @NotNull JavaType javaType, @NotNull StorIOSQLiteColumn storIOSQLiteColumn) {
-        this.enclosingElement = enclosingElement;
-        this.element = element;
-        this.fieldName = fieldName;
-        this.javaType = javaType;
-        this.storIOSQLiteColumn = storIOSQLiteColumn;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        StorIOSQLiteColumnMeta that = (StorIOSQLiteColumnMeta) o;
-
-        if (!enclosingElement.equals(that.enclosingElement)) return false;
-        if (!element.equals(that.element)) return false;
-        if (!fieldName.equals(that.fieldName)) return false;
-        if (javaType != that.javaType) return false;
-        return storIOSQLiteColumn.equals(that.storIOSQLiteColumn);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = enclosingElement.hashCode();
-        result = 31 * result + element.hashCode();
-        result = 31 * result + fieldName.hashCode();
-        result = 31 * result + javaType.hashCode();
-        result = 31 * result + storIOSQLiteColumn.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "StorIOSQLiteColumnMeta{" +
-                "enclosingElement=" + enclosingElement +
-                ", element=" + element +
-                ", fieldName='" + fieldName + '\'' +
-                ", javaType=" + javaType +
-                ", storIOSQLiteColumn=" + storIOSQLiteColumn +
-                '}';
+    public StorIOSQLiteColumnMeta(
+            @NotNull Element enclosingElement,
+            @NotNull Element element,
+            @NotNull String fieldName,
+            @NotNull JavaType javaType,
+            @NotNull StorIOSQLiteColumn storIOColumn) {
+        super(enclosingElement, element, fieldName, javaType, storIOColumn);
     }
 }
diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteTypeMeta.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteTypeMeta.java
index 7e9835c..f03d715 100644
--- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteTypeMeta.java
+++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/StorIOSQLiteTypeMeta.java
@@ -1,64 +1,16 @@
 package com.pushtorefresh.storio.sqlite.annotations.processor.introspection;
 
+import com.pushtorefresh.storio.common.annotations.processor.introspection.StorIOTypeMeta;
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.HashMap;
-import java.util.Map;
+public class StorIOSQLiteTypeMeta extends StorIOTypeMeta<StorIOSQLiteType, StorIOSQLiteColumnMeta> {
 
-public class StorIOSQLiteTypeMeta {
-
-    @NotNull
-    public final String simpleName;
-
-    @NotNull
-    public final String packageName;
-
-    @NotNull
-    public final StorIOSQLiteType storIOSQLiteType;
-
-    /**
-     * Yep, this is MODIFIABLE Map, please use it carefully
-     */
-    @NotNull
-    public final Map<String, StorIOSQLiteColumnMeta> columns = new HashMap<String, StorIOSQLiteColumnMeta>();
-
-    public StorIOSQLiteTypeMeta(@NotNull String simpleName, @NotNull String packageName, @NotNull StorIOSQLiteType storIOSQLiteType) {
-        this.simpleName = simpleName;
-        this.packageName = packageName;
-        this.storIOSQLiteType = storIOSQLiteType;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        StorIOSQLiteTypeMeta that = (StorIOSQLiteTypeMeta) o;
-
-        if (!simpleName.equals(that.simpleName)) return false;
-        if (!packageName.equals(that.packageName)) return false;
-        if (!storIOSQLiteType.equals(that.storIOSQLiteType)) return false;
-        return columns.equals(that.columns);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = simpleName.hashCode();
-        result = 31 * result + packageName.hashCode();
-        result = 31 * result + storIOSQLiteType.hashCode();
-        result = 31 * result + columns.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "StorIOSQLiteTypeMeta{" +
-                "simpleName='" + simpleName + '\'' +
-                ", packageName='" + packageName + '\'' +
-                ", storIOSQLiteType=" + storIOSQLiteType +
-                ", columns=" + columns +
-                '}';
+    public StorIOSQLiteTypeMeta(
+            @NotNull String simpleName,
+            @NotNull String packageName,
+            @NotNull StorIOSQLiteType storIOType) {
+        super(simpleName, packageName, storIOType);
     }
-}
+}
\ No newline at end of file
diff --git a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java
index 5f3e61c..9cd91db 100644
--- a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java
+++ b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java
@@ -2,7 +2,7 @@
 
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
-import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType;
+import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta;
 import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta;
 import com.squareup.javapoet.JavaFile;
diff --git a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaTypeTest.java b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaTypeTest.java
deleted file mode 100644
index 2d80aeb..0000000
--- a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/introspection/JavaTypeTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.pushtorefresh.storio.sqlite.annotations.processor.introspection;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.junit.Test;
-
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.BOOLEAN;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.BOOLEAN_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.BYTE_ARRAY;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.DOUBLE;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.DOUBLE_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.FLOAT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.FLOAT_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.INTEGER;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.INTEGER_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.LONG;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.LONG_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.SHORT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.SHORT_OBJECT;
-import static com.pushtorefresh.storio.sqlite.annotations.processor.introspection.JavaType.STRING;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class JavaTypeTest {
-
-    @NotNull
-    private static TypeMirror mockTypeMirror(@Nullable TypeKind typeKind, @Nullable String typeName) {
-        final TypeMirror typeMirror = mock(TypeMirror.class);
-
-        when(typeMirror.getKind())
-                .thenReturn(typeKind);
-
-        when(typeMirror.toString())
-                .thenReturn(typeName);
-
-        return typeMirror;
-    }
-
-    @Test
-    public void fromBoolean() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.BOOLEAN, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(BOOLEAN);
-    }
-
-    @Test
-    public void fromBooleanObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Boolean.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(BOOLEAN_OBJECT);
-    }
-
-    @Test
-    public void fromShort() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.SHORT, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(SHORT);
-    }
-
-    @Test
-    public void fromShortObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Short.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(SHORT_OBJECT);
-    }
-
-    @Test
-    public void fromInteger() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.INT, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(INTEGER);
-    }
-
-    @Test
-    public void fromIntegerObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Integer.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(INTEGER_OBJECT);
-    }
-
-    @Test
-    public void fromLong() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.LONG, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(LONG);
-    }
-
-    @Test
-    public void fromLongObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Long.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(LONG_OBJECT);
-    }
-
-    @Test
-    public void fromFloat() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.FLOAT, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(FLOAT);
-    }
-
-    @Test
-    public void fromFloatObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Float.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(FLOAT_OBJECT);
-    }
-
-    @Test
-    public void fromDouble() {
-        final TypeMirror typeMirror = mockTypeMirror(TypeKind.DOUBLE, null);
-        assertThat(JavaType.from(typeMirror)).isEqualTo(DOUBLE);
-    }
-
-    @Test
-    public void fromDoubleObject() {
-        final TypeMirror typeMirror = mockTypeMirror(null, Double.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(DOUBLE_OBJECT);
-    }
-
-    @Test
-    public void fromString() {
-        final TypeMirror typeMirror = mockTypeMirror(null, String.class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(STRING);
-    }
-
-    @Test
-    public void fromByteArray() {
-        final TypeMirror typeMirror = mockTypeMirror(null, byte[].class.getCanonicalName());
-        assertThat(JavaType.from(typeMirror)).isEqualTo(BYTE_ARRAY);
-    }
-}
