<|start_filename|>example/build/generated/source/apt/normal/debug/com/github/gfx/android/orma/example/orma/Item2_Deleter.java<|end_filename|>
package com.github.gfx.android.orma.example.orma;

import androidx.annotation.NonNull;
import com.github.gfx.android.orma.function.Function1;
import com.github.gfx.android.orma.rx.RxDeleter;
import com.github.gfx.android.orma.rx.RxOrmaConnection;
import java.util.Arrays;
import java.util.Collection;

public class Item2_Deleter extends RxDeleter<Item2, Item2_Deleter> {
  final Item2_Schema schema;

  public Item2_Deleter(RxOrmaConnection conn, Item2_Schema schema) {
    super(conn);
    this.schema = schema;
  }

  public Item2_Deleter(Item2_Deleter that) {
    super(that);
    this.schema = that.getSchema();
  }

  public Item2_Deleter(Item2_Relation relation) {
    super(relation);
    this.schema = relation.getSchema();
  }

  @Override
  public Item2_Deleter clone() {
    return new Item2_Deleter(this);
  }

  @NonNull
  @Override
  public Item2_Schema getSchema() {
    return schema;
  }

  public Item2_Deleter category1Eq(@NonNull Category category1) {
    return where(schema.category1, "=", category1.id);
  }

  public Item2_Deleter category1Eq(long category1Id) {
    return where(schema.category1, "=", category1Id);
  }

  public Item2_Deleter category1(
      @NonNull Function1<Category_AssociationCondition, Category_AssociationCondition> block) {
    return block.apply(new Category_AssociationCondition(getConnection(), schema.category1.associationSchema)).appendTo(this);
  }

  public Item2_Deleter category2IsNull() {
    return where(schema.category2, " IS NULL");
  }

  public Item2_Deleter category2IsNotNull() {
    return where(schema.category2, " IS NOT NULL");
  }

  public Item2_Deleter category2Eq(@NonNull Category category2) {
    return where(schema.category2, "=", category2.id);
  }

  public Item2_Deleter category2Eq(long category2Id) {
    return where(schema.category2, "=", category2Id);
  }

  public Item2_Deleter category2(
      @NonNull Function1<Category_AssociationCondition, Category_AssociationCondition> block) {
    return block.apply(new Category_AssociationCondition(getConnection(), schema.category2.associationSchema)).appendTo(this);
  }

  public Item2_Deleter nameEq(@NonNull String name) {
    return where(schema.name, "=", name);
  }

  public Item2_Deleter nameNotEq(@NonNull String name) {
    return where(schema.name, "<>", name);
  }

  public Item2_Deleter nameIn(@NonNull Collection<String> values) {
    return in(false, schema.name, values);
  }

  public Item2_Deleter nameNotIn(@NonNull Collection<String> values) {
    return in(true, schema.name, values);
  }

  public final Item2_Deleter nameIn(@NonNull String... values) {
    return nameIn(Arrays.asList(values));
  }

  public final Item2_Deleter nameNotIn(@NonNull String... values) {
    return nameNotIn(Arrays.asList(values));
  }

  public Item2_Deleter nameLt(@NonNull String name) {
    return where(schema.name, "<", name);
  }

  public Item2_Deleter nameLe(@NonNull String name) {
    return where(schema.name, "<=", name);
  }

  public Item2_Deleter nameGt(@NonNull String name) {
    return where(schema.name, ">", name);
  }

  public Item2_Deleter nameGe(@NonNull String name) {
    return where(schema.name, ">=", name);
  }
}


<|start_filename|>annotations/src/main/java/com/github/gfx/android/orma/annotation/Column.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.annotation;

import androidx.annotation.LongDef;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Column {

    /**
     * @return The column name in SQLite tables. It is case-insensitive.
     */
    String value() default "";

    /**
     * @return Create an index for the column.
     */
    boolean indexed() default false;

    /**
     * Specifies {@code UNIQUE} constraint. Shortcut of {@code uniqueOnConflict = OnConflict.ABORT}.
     *
     * @return True if the column value is unique in the table.
     */
    boolean unique() default false;

    /**
     * Specifies {@code UNIQUE} constraint with an {@code ON CONFLICT} clause.
     *
     * @return One of {@code OnConflict.ABORT}, {@code FAIL}, {@code IGNORE},
     * {@code REPLACE}, or {@code ROLLBACK}
     */
    @OnConflict int uniqueOnConflict() default OnConflict.NONE;

    ForeignKeyAction onDelete() default ForeignKeyAction.CASCADE;

    ForeignKeyAction onUpdate() default ForeignKeyAction.CASCADE;

    /**
     * The {@code DEFAULT} expression for the column. Currently it is only used in migration.
     *
     * @return An SQLite expression. For example. {@code "''"} for an empty string, and {@code "0"} for a literal zero,
     */
    String defaultExpr() default "";

    /**
     * Specifies how the column is compared.
     *
     * @return One of {@code Column.Collate.BINARY} (the default), {@code NOCASE} or {@code RTRIM}
     */
    Collate collate() default Collate.BINARY;

    /**
     * Specifies a storage type for the column to suppress automatic migration for existing tables.
     * <strong>Not recommended</strong>.  Will become deprecated in a future.
     *
     * @return An SQLite data type. e.g. {@code "TEXT"}, {@code "FLOAT"}, {@code "INTEGER"}, or {@code "BLOB"}
     */
    String storageType() default "";

    /**
     * @return Flags that control which helpers to generate
     */
    @Helpers long helpers() default Helpers.AUTO;

    /**
     * SQLite's {@code COLLATE} algorithms.
     *
     * @see <a href="https://www.sqlite.org/datatype3.html#collation">https://www.sqlite.org/datatype3.html#collation</a>
     */
    enum Collate {
        /**
         * <blockquote cite="https://www.sqlite.org/datatype3.html#collation">Compares string data using memcmp(), regardless
         * of text encoding.</blockquote>
         */
        BINARY,
        /**
         * <blockquote cite="https://www.sqlite.org/datatype3.html#collation">The same as binary, except the 26 upper
         * case characters of ASCII are folded to their lower case equivalents before the comparison is performed. Note that
         * only ASCII characters are case folded. SQLite does not attempt to do full UTF case folding due to the size of the
         * tables required.</blockquote>
         */
        NOCASE,
        /**
         * <blockquote cite="https://www.sqlite.org/datatype3.html#collation">The same as binary, except that trailing space
         * characters are ignored.</blockquote>
         */
        RTRIM
    }

    /**
     * SQLite's <code>ON DELETE</code> and <code>ON UPDATE</code> actions.
     *
     * @see <a href="https://www.sqlite.org/foreignkeys.html">SQLite Foreign Key Support</a>
     */
    enum ForeignKeyAction {
        NO_ACTION,
        RESTRICT,
        SET_NULL,
        SET_DEFAULT,
        CASCADE,
    }

    @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.PARAMETER, ElementType.METHOD})
    @Retention(RetentionPolicy.CLASS)
    @LongDef(flag = true, value = {
            Helpers.NONE,
            Helpers.AUTO,
            Helpers.CONDITION_EQ,
            Helpers.CONDITION_NOT_EQ,
            Helpers.CONDITION_IS_NULL,
            Helpers.CONDITION_IS_NOT_NULL,
            Helpers.CONDITION_IN,
            Helpers.CONDITION_NOT_IN,
            Helpers.CONDITION_LT,
            Helpers.CONDITION_LE,
            Helpers.CONDITION_GT,
            Helpers.CONDITION_GE,
            Helpers.CONDITION_BETWEEN,
            Helpers.ORDER_IN_ASC,
            Helpers.ORDER_IN_DESC,
            Helpers.PLUCK,
            Helpers.MIN,
            Helpers.MAX,
            Helpers.SUM,
            Helpers.AVG,
    })
    @interface Helpers {

        long NONE = 0;
        long AUTO = 1;

        long CONDITION_EQ = AUTO << 1;
        long CONDITION_NOT_EQ = CONDITION_EQ << 1;
        long CONDITION_IS_NULL = CONDITION_NOT_EQ << 1;
        long CONDITION_IS_NOT_NULL = CONDITION_IS_NULL << 1;
        long CONDITION_IN = CONDITION_IS_NOT_NULL << 1;
        long CONDITION_NOT_IN = CONDITION_IN << 1;
        long CONDITION_GLOB = CONDITION_NOT_IN << 1;
        long CONDITION_NOT_GLOB = CONDITION_GLOB << 1;
        long CONDITION_LIKE = CONDITION_NOT_GLOB << 1;
        long CONDITION_NOT_LIKE = CONDITION_LIKE << 1;

        long CONDITION_LT = CONDITION_NOT_LIKE << 1;
        long CONDITION_LE = CONDITION_LT << 1;
        long CONDITION_GT = CONDITION_LE << 1;
        long CONDITION_GE = CONDITION_GT << 1;
        long CONDITION_BETWEEN = CONDITION_GE << 1;

        long CONDITIONS = CONDITION_EQ | CONDITION_NOT_EQ | CONDITION_IS_NULL | CONDITION_IS_NOT_NULL
                | CONDITION_IN | CONDITION_NOT_IN
                | CONDITION_GLOB | CONDITION_NOT_GLOB | CONDITION_LIKE | CONDITION_NOT_LIKE
                | CONDITION_LT | CONDITION_LE | CONDITION_GT | CONDITION_GE | CONDITION_BETWEEN;

        long ORDER_IN_ASC = CONDITION_BETWEEN << 1;
        long ORDER_IN_DESC = ORDER_IN_ASC << 1;

        long ORDERS = ORDER_IN_ASC | ORDER_IN_DESC;

        long PLUCK = ORDER_IN_DESC << 1;

        long MIN = PLUCK << 1;
        long MAX = MIN << 1;
        long SUM = MAX << 1;
        long AVG = SUM << 1;

        long AGGREGATORS = MIN | MAX | SUM | AVG;

        long ALL = CONDITIONS | ORDERS | PLUCK | AGGREGATORS;
    }
}


<|start_filename|>processor/src/main/java/com/github/gfx/android/orma/processor/ProcessingContext.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.processor;

import com.github.gfx.android.orma.annotation.Database;
import com.github.gfx.android.orma.processor.exception.ProcessingException;
import com.github.gfx.android.orma.processor.generator.SqlGenerator;
import com.github.gfx.android.orma.processor.model.DatabaseDefinition;
import com.github.gfx.android.orma.processor.model.SchemaDefinition;
import com.github.gfx.android.orma.processor.model.TypeAdapterDefinition;
import com.github.gfx.android.orma.processor.util.SqlTypes;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor8;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import androidx.annotation.Nullable;

public class ProcessingContext {

    public final ProcessingEnvironment processingEnv;

    public final Types typeUtils;

    public final List<ProcessingException> errors = new ArrayList<>();

    public final List<DatabaseDefinition> databases = new ArrayList<>();

    public final Map<TypeName, SchemaDefinition> schemaMap = new LinkedHashMap<>(); // the order matters

    public final SqlGenerator sqlg = new SqlGenerator();

    private final Map<TypeName, TypeAdapterDefinition> typeAdapterMap = new HashMap<>();

    public ProcessingContext(ProcessingEnvironment processingEnv) {
        this.processingEnv = processingEnv;
        typeUtils = processingEnv.getTypeUtils();
        for (TypeAdapterDefinition typeAdapterDefinition : TypeAdapterDefinition.BUILTINS) {
            addTypeAdapterDefinition(typeAdapterDefinition);
        }
    }

    public void addError(String message, Element element) {
        addError(new ProcessingException(message, element));
    }

    public void addError(ProcessingException e) {
        errors.add(e);
    }

    public void printErrors() {
        Messager messager = processingEnv.getMessager();

        errors.forEach(error -> {
            if (error.getCause() != null) {
                StringWriter s = new StringWriter();
                s.append(error.getMessage());
                s.append('\n');
                error.getCause().printStackTrace(new PrintWriter(s));
                messager.printMessage(Diagnostic.Kind.ERROR, s.toString(), error.element);
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, error.getMessage(), error.element);
            }
        });
    }

    public Elements getElements() {
        return processingEnv.getElementUtils();
    }

    public void addDatabaseDefinition(DatabaseDefinition databaseDefinition) {
        databases.add(databaseDefinition);
    }

    public void addTypeAdapterDefinition(TypeAdapterDefinition typeAdapterDefinition) {
        // warn if non-built-in type adapters are overridden
        TypeAdapterDefinition previous = typeAdapterMap.get(typeAdapterDefinition.targetType);
        if (previous != null && !previous.builtin) {
            warn("Duplicated @StaticTypeAdapter for " + typeAdapterDefinition.targetType, typeAdapterDefinition.element);
        }
        typeAdapterMap.put(typeAdapterDefinition.targetType, typeAdapterDefinition);
    }

    public SchemaDefinition getSchemaDef(TypeName modelClassName) {
        return schemaMap.get(modelClassName);
    }

    public TypeMirror getTypeMirrorOf(Type type) {
        return getTypeElement(type).asType();
    }

    public TypeElement getTypeElement(CharSequence fqName) {
        TypeElement typeElement = processingEnv.getElementUtils().getTypeElement(fqName);
        if (typeElement == null) {
            throw new RuntimeException("No such class: " + fqName);
        }
        return typeElement;
    }

    public TypeElement getTypeElement(TypeMirror typeMirror) {
        return typeMirror.accept(new SimpleTypeVisitor8<TypeElement, TypeMirror>() {
            @Override
            public TypeElement visitDeclared(DeclaredType declaredType, TypeMirror typeMirror) {
                return (TypeElement) declaredType.asElement();
            }
        }, typeMirror);
    }

    public TypeElement getTypeElement(Type name) {
        return getTypeElement(name.getTypeName());
    }

    public TypeAdapterDefinition getTypeAdapter(TypeName typeName) {
        return typeAdapterMap.get(typeName);
    }

    @Nullable
    public TypeAdapterDefinition findTypeAdapter(TypeMirror typeMirror) {
        if (typeMirror.getKind().isPrimitive()) {
            return null;
        }

        TypeName typeName = TypeName.get(typeMirror);
        if (SqlTypes.canHandle(typeName)) {
            return null;
        }

        TypeAdapterDefinition typeAdapter = typeAdapterMap.get(typeName);
        if (typeAdapter != null) {
            return typeAdapter;
        }

        // find the best matched one
        Map<TypeName, Integer> classHierarchyMap = buildClassHierarchyMap(typeMirror);
        if (classHierarchyMap == null) {
            return null; // no corresponding type element, e.g. byte[]
        }
        return typeAdapterMap.keySet()
                .stream()
                .filter(classHierarchyMap::containsKey)
                .sorted((a, b) -> classHierarchyMap.get(b) - classHierarchyMap.get(a))
                .findFirst()
                .map(typeAdapterMap::get)
                .orElse(null);
    }

    private Map<TypeName, Integer> buildClassHierarchyMap(TypeMirror typeMirror) {
        TypeElement element = (TypeElement) typeUtils.asElement(typeMirror);
        if (element == null) {
            return null;
        }
        Map<TypeName, Integer> map = new HashMap<>();
        int distance = 0;
        while (!element.toString().equals(Object.class.getName())) {
            map.put(ClassName.get(element), distance);
            collectInterfaces(map, element, ++distance);

            element = (TypeElement) typeUtils.asElement(element.getSuperclass());
        }
        return map;
    }

    private void collectInterfaces(Map<TypeName, Integer> map, TypeElement element, int distance) {
        element.getInterfaces().forEach((interfaceMirror) -> {
            TypeElement interfaceElement = (TypeElement) typeUtils.asElement(interfaceMirror);
            map.put(ClassName.get(interfaceElement), distance);

            collectInterfaces(map, interfaceElement, distance + 1);
        });

    }


    public boolean isSameType(TypeMirror t1, TypeMirror t2) {
        return typeUtils.isSameType(t1, t2);
    }

    public void setupDefaultDatabaseIfNeeded() {
        if (databases.isEmpty()) {
            SchemaDefinition schema = getFirstSchema();
            databases.add(new DatabaseDefinition(this,
                    schema.getPackageName(),
                    Database.DEFAULT_DATABASE_CLASS_NAME,
                    Database.DEFAULT_RX_JAVA_SUPPORT));
        }
    }

    public SchemaDefinition getFirstSchema() {
        return schemaMap.values().iterator().next();
    }

    public boolean isRxJavaSupport(SchemaDefinition schema) {
        if (databases.isEmpty()) {
            return Database.DEFAULT_RX_JAVA_SUPPORT;
        }

        long targetCount = databases
                .stream()
                .filter((database) -> database.getSchemas().contains(schema))
                .count();

        long rxJavaSupportCount = databases
                .stream()
                .filter((database) -> database.getSchemas().contains(schema) && database.isRxJavaSupport())
                .count();

        if (rxJavaSupportCount == 0L) {
            return false;
        }

        if (rxJavaSupportCount < targetCount) {
            addError(schema.getModelClassName().simpleName() + " is included in different RxJavaSupport databases.",
                    schema.getElement());
        }

        return true;
    }

    public void note(String message) {
        processingEnv.getMessager()
                .printMessage(Diagnostic.Kind.NOTE, "[" + OrmaProcessor.TAG + "] " + message);
    }

    public void warn(String message, Element element) {
        processingEnv.getMessager()
                .printMessage(Diagnostic.Kind.WARNING, "[" + OrmaProcessor.TAG + "] " + message, element);
    }

}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/QueryObservableTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.event.DataSetChangedEvent;
import com.github.gfx.android.orma.test.model.Author;
import com.github.gfx.android.orma.test.model.Author_Selector;
import com.github.gfx.android.orma.test.model.OrmaDatabase;
import com.github.gfx.android.orma.test.toolbox.OrmaFactory;
import com.github.gfx.android.orma.test.toolbox.TestUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.List;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assume.assumeFalse;

@RunWith(AndroidJUnit4.class)
public class QueryObservableTest {

    OrmaDatabase db;

    @Before
    public void setUp() throws Exception {
        db = OrmaFactory.create();

        db.insertIntoAuthor(Author.create("foo"));
    }

    @Test
    public void example() throws Exception {
        final List<String> result = new ArrayList<>();

        Observable<Author_Selector> observable = db.relationOfAuthor()
                .createQueryObservable();
        Disposable subscription = observable.flatMap(new Function<Author_Selector, Observable<Author>>() {
            @Override
            public Observable<Author> apply(Author_Selector selector) throws Exception {
                return selector.executeAsObservable();
            }
        })
                .map(new Function<Author, String>() {
                    @Override
                    public String apply(Author author) throws Exception {
                        return author.name;
                    }
                })
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        result.add(s);
                    }
                });

        // fire an event
        db.insertIntoAuthor(Author.create("bar"));
        assertThat(result, contains("foo", "bar"));

        // fire another event
        result.clear();
        db.insertIntoAuthor(Author.create("baz"));
        assertThat(result, contains("foo", "bar", "baz"));

        subscription.dispose();
    }

    @Test
    public void autoDispose() throws Exception {
        assumeFalse(TestUtils.runOnAndroid()); // FIXME

        final List<String> result = new ArrayList<>();

        Disposable subscription = db.relationOfAuthor()
                .<Author_Selector>createQueryObservable()
                .flatMap(new Function<Author_Selector, Observable<Author>>() {
                    @Override
                    public Observable<Author> apply(Author_Selector selector) throws Exception {
                        return selector.executeAsObservable();
                    }
                })
                .map(new Function<Author, String>() {
                    @Override
                    public String apply(Author author) throws Exception {
                        return author.name;
                    }
                })
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        result.add(s);
                    }
                });
        subscription.dispose();

        System.gc();

        // fire an event, but the observer is disposed by GC
        db.insertIntoAuthor(Author.create("bar"));
        assertThat(result, hasSize(0));
    }

    @SuppressWarnings({"deprecation"})
    @Test
    public void eventTypes() throws Exception {
        final List<DataSetChangedEvent.Type> result = new ArrayList<>();

        Observable<DataSetChangedEvent<Author_Selector>> observable = db.relationOfAuthor()
                .createEventObservable();
        observable.map(new Function<DataSetChangedEvent<Author_Selector>, DataSetChangedEvent.Type>() {
            @Override
            public DataSetChangedEvent.Type apply(DataSetChangedEvent<Author_Selector> event) throws Exception {
                return event.getType();
            }
        }).subscribe(new Consumer<DataSetChangedEvent.Type>() {
            @Override
            public void accept(DataSetChangedEvent.Type type) throws Exception {
                result.add(type);
            }
        });

        // fire an INSERT event
        db.insertIntoAuthor(Author.create("bar"));
        assertThat(result, contains(DataSetChangedEvent.Type.INSERT));

        // fire an UPDATE event
        result.clear();
        db.updateAuthor().note("test note").execute();
        assertThat(result, contains(DataSetChangedEvent.Type.UPDATE));

        // fire an DELETE event
        result.clear();
        db.deleteFromAuthor().execute();
        assertThat(result, contains(DataSetChangedEvent.Type.DELETE));

        // fire an TRANSACTION
        result.clear();
        db.transactionSync(new Runnable() {
            @Override
            public void run() {
                db.insertIntoAuthor(Author.create("foo"));
                db.insertIntoAuthor(Author.create("bar"));
                db.insertIntoAuthor(Author.create("baz"));
            }
        });
        assertThat(result, hasSize(1));
        assertThat(result, contains(DataSetChangedEvent.Type.TRANSACTION));

        result.clear();
        db.transactionNonExclusiveSync(new Runnable() {
            @Override
            public void run() {
                db.insertIntoAuthor(Author.create("FOO"));
                db.insertIntoAuthor(Author.create("BAR"));
                db.insertIntoAuthor(Author.create("BAZ"));
            }
        });
        assertThat(result, hasSize(1));
        assertThat(result, contains(DataSetChangedEvent.Type.TRANSACTION));
    }

}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/model/ModelWithTypeAdapters.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.test.model;

import com.github.gfx.android.orma.annotation.Column;
import com.github.gfx.android.orma.annotation.PrimaryKey;
import com.github.gfx.android.orma.annotation.Table;
import com.github.gfx.android.orma.test.toolbox.EnumA;
import com.github.gfx.android.orma.test.toolbox.EnumB;
import com.github.gfx.android.orma.test.toolbox.IntTuple2;
import com.github.gfx.android.orma.test.toolbox.MutableInt;
import com.github.gfx.android.orma.test.toolbox.MutableLong;

import android.net.Uri;
import androidx.annotation.Nullable;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Currency;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;

@Table
public class ModelWithTypeAdapters {

    @PrimaryKey
    public long id;

    @Column(indexed = true)
    public List<String> list;

    @Column(indexed = true)
    public Set<String> set;

    @Column(indexed = true)
    public ArrayList<String> arrayList;

    @Column(indexed = true)
    public HashSet<String> hashSet;

    @Column(indexed = true)
    public Uri uri;

    @Column(indexed = true)
    public Date date;

    @Column(indexed = true)
    public java.sql.Date sqlDate;

    @Column(indexed = true)
    public java.sql.Time sqlTime;

    @Column(indexed = true)
    public java.sql.Timestamp sqlTimestamp;

    @Column(indexed = true)
    public BigDecimal bigDecimal;

    @Column(indexed = true)
    public BigInteger bigInteger;

    @Column(indexed = true)
    public UUID uuid;

    @Column(indexed = true)
    public Currency currency;

    @Column(indexed = true)
    public ByteBuffer byteBuffer;

    @Column(indexed = true)
    public IntTuple2 intTuple2;

    @Column(indexed = true)
    public MutableInt mutableInt;

    @Column(indexed = true)
    public MutableLong mutableLong;

    @Nullable
    @Column(indexed = true)
    public List<String> nullableList;

    @Nullable
    @Column(indexed = true)
    public Set<String> nullableSet;

    @Nullable
    @Column(indexed = true)
    public LinkedList<String> nullableLinkedList;

    @Nullable
    @Column(indexed = true)
    public LinkedHashSet<String> nullableLinkedHashSet;

    @Nullable
    @Column(indexed = true)
    public Uri nullableUri;

    @Nullable
    @Column(indexed = true)
    public Date nullableDate;

    @Column(indexed = true)
    @Nullable
    public IntTuple2 nullableIntTuple2;

    @Column(indexed = true)
    @Nullable
    public ByteBuffer nullableByteBuffer;

    @Column(indexed = true)
    public EnumA enumA;

    @Column(indexed = true)
    public EnumB enumB;
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/model/Book.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.test.model;

import com.github.gfx.android.orma.SingleAssociation;
import com.github.gfx.android.orma.annotation.Column;
import com.github.gfx.android.orma.annotation.PrimaryKey;
import com.github.gfx.android.orma.annotation.Table;

import androidx.annotation.Nullable;

@Table
public class Book {

    @PrimaryKey
    public long bookId;

    @Column(indexed = true)
    public String title;

    @Column
    @Nullable
    public String content;

    @Column
    public boolean inPrint;

    @Column(indexed = true)
    public long price = 100;

    @Column(indexed = true)
    public SingleAssociation<Publisher> publisher;

    public static Book create(String title, Publisher publisher) {
        Book book = new Book();
        book.title = title;
        book.publisher = SingleAssociation.just(publisher);
        return book;
    }
}


<|start_filename|>library/src/main/java/com/github/gfx/android/orma/rx/RxSelector.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.rx;

import com.github.gfx.android.orma.ColumnDef;
import com.github.gfx.android.orma.OrmaConnection;
import com.github.gfx.android.orma.Relation;
import com.github.gfx.android.orma.Selector;

import android.database.Cursor;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;

import java.util.concurrent.Callable;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Single;

public abstract class RxSelector<Model, S extends RxSelector<Model, ?>> extends Selector<Model, S> {

    public RxSelector(@NonNull OrmaConnection conn) {
        super(conn);
    }

    public RxSelector(@NonNull Relation<Model, ?> relation) {
        super(relation);
    }

    public RxSelector(@NonNull Selector<Model, ?> selector) {
        super(selector);
    }

    @CheckResult
    @NonNull
    public Single<Integer> countAsSingle() {
        return Single.fromCallable(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return count();
            }
        });
    }

    @NonNull
    public <T> Observable<T> pluckAsObservable(final ColumnDef<Model, T> column) {
        return Observable.create(new ObservableOnSubscribe<T>() {
            @Override
            public void subscribe(ObservableEmitter<T> emitter) throws Exception {
                Cursor cursor = executeWithColumns(column.getQualifiedName());
                try {
                    for (int pos = 0; !emitter.isDisposed() && cursor.moveToPosition(pos); pos++) {
                        emitter.onNext(column.getFromCursor(conn, cursor, 0));
                    }
                } finally {
                    cursor.close();
                }
                emitter.onComplete();
            }
        });
    }

    @NonNull
    public Observable<Model> executeAsObservable() {
        return Observable.create(new ObservableOnSubscribe<Model>() {
            @Override
            public void subscribe(ObservableEmitter<Model> emitter) throws Exception {
                final Cursor cursor = execute();
                try {
                    for (int pos = 0; !emitter.isDisposed() && cursor.moveToPosition(pos); pos++) {
                        emitter.onNext(newModelFromCursor(cursor));
                    }
                } finally {
                    cursor.close();
                }
                emitter.onComplete();
            }
        });
    }
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/PrimaryKeyWithTypeAdapterTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.ModelFactory;
import com.github.gfx.android.orma.test.model.ModelWithCustomPrimaryKey;
import com.github.gfx.android.orma.test.model.OrmaDatabase;
import com.github.gfx.android.orma.test.toolbox.EnumA;
import com.github.gfx.android.orma.test.toolbox.OrmaFactory;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

@RunWith(AndroidJUnit4.class)
public class PrimaryKeyWithTypeAdapterTest {

    OrmaDatabase db;

    @Before
    public void setUp() throws Exception {
        db = OrmaFactory.create();
    }

    @Test
    public void bigIntegerPrimaryKey() throws Exception {
        final ModelWithCustomPrimaryKey model = db
                .createModelWithCustomPrimaryKey(new ModelFactory<ModelWithCustomPrimaryKey>() {
                    @NonNull
                    @Override
                    public ModelWithCustomPrimaryKey call() {
                        return ModelWithCustomPrimaryKey.create(EnumA.FOO);
                    }
                });

        ModelWithCustomPrimaryKey.Holder holder = db.createHolder(new ModelFactory<ModelWithCustomPrimaryKey.Holder>() {
            @NonNull
            @Override
            public ModelWithCustomPrimaryKey.Holder call() {
                ModelWithCustomPrimaryKey.Holder holder = new ModelWithCustomPrimaryKey.Holder();
                holder.object = model;
                return holder;
            }
        });

        assertThat(holder.object.id, is(EnumA.FOO));

        assertThat(db.selectFromHolder().objectEq(EnumA.FOO).value().object.id, is(EnumA.FOO));
        assertThat(db.selectFromHolder().objectEq(model).value().object.id, is(EnumA.FOO));
    }
}


<|start_filename|>core/src/main/java/com/github/gfx/android/orma/core/DefaultDatabase.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.core;

import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import androidx.annotation.NonNull;

public class DefaultDatabase implements Database {

    private final SQLiteDatabase database;

    private DefaultDatabase(SQLiteDatabase database) {
        this.database = database;
    }

    @Override
    public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) {
        return database.insertWithOnConflict(table, nullColumnHack, initialValues, conflictAlgorithm);
    }

    @Override
    public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
        return database.update(table, values, whereClause, whereArgs);
    }

    @Override
    public Cursor rawQuery(String sql, String[] selectionArgs) {
        return database.rawQuery(sql, selectionArgs);
    }

    @Override
    public DatabaseStatement compileStatement(String sql) {
        return new DefaultDatabaseStatement(database.compileStatement(sql));
    }

    @Override
    public void beginTransaction() {
        database.beginTransaction();
    }

    @Override
    public void beginTransactionNonExclusive() {
        database.beginTransactionNonExclusive();
    }

    @Override
    public void setTransactionSuccessful() {
        database.setTransactionSuccessful();
    }

    @Override
    public void endTransaction() {
        database.endTransaction();
    }

    @Override
    public boolean inTransaction() {
        return database.inTransaction();
    }

    @Override
    public void execSQL(String sql) {
        database.execSQL(sql);
    }

    @Override
    public void execSQL(String sql, Object[] bindArgs) {
        database.execSQL(sql, bindArgs);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void setForeignKeyConstraintsEnabled(boolean enable) {
        database.setForeignKeyConstraintsEnabled(enable);
    }

    @Override
    public boolean enableWriteAheadLogging() {
        return database.enableWriteAheadLogging();
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public boolean isWriteAheadLoggingEnabled() {
        return database.isWriteAheadLoggingEnabled();
    }

    @Override
    public long longForQuery(String query, String[] selectionArgs) {
        return DatabaseUtils.longForQuery(database, query, selectionArgs);
    }

    @Override
    public int getVersion() {
        return database.getVersion();
    }

    @Override
    public void setVersion(int version) {
        database.setVersion(version);
    }

    @Override
    public long insertOrThrow(String table, String nullColumnHack, ContentValues values) {
        return database.insertOrThrow(table, nullColumnHack, values);
    }

    @Override
    public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit) {
        return database.query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
    }

    @Override
    public long queryNumEntries(String table, String selection, String[] selectionArgs) {
        return DatabaseUtils.queryNumEntries(database, table, selection, selectionArgs);
    }

    @Override
    public void close() {
        database.close();
    }

    public static class Provider implements DatabaseProvider {

        @NonNull
        @Override
        public Database provideOnDiskDatabase(@NonNull Context context, @NonNull String name, int mode) {
            return new DefaultDatabase(context.openOrCreateDatabase(name, mode, null, null));
        }

        @NonNull
        @Override
        public Database provideOnMemoryDatabase(@NonNull Context context) {
            return new DefaultDatabase(SQLiteDatabase.create(null));
        }
    }
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/MigrationEngineTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.OrmaConnection;
import com.github.gfx.android.orma.migration.ManualStepMigration;
import com.github.gfx.android.orma.migration.MigrationEngine;
import com.github.gfx.android.orma.migration.OrmaMigration;
import com.github.gfx.android.orma.test.model.OrmaDatabase;
import com.github.gfx.android.orma.test.toolbox.OrmaFactory;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import android.content.Context;

import java.util.concurrent.atomic.AtomicInteger;

import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

@RunWith(AndroidJUnit4.class)
public class MigrationEngineTest {

    Context getContext() {
        return ApplicationProvider.getApplicationContext();
    }

    @Before
    public void setUp() throws Exception {
    }

    @Test
    public void startEmpty() throws Exception {
        MigrationEngine migration = OrmaMigration.builder(getContext())
                .versionForManualStepMigration(1)
                .schemaHashForSchemaDiffMigration(OrmaDatabase.SCHEMA_HASH)
                .build();

        OrmaConnection conn = OrmaFactory.builder()
                .migrationEngine(migration)
                .build()
                .getConnection();

        migration.start(conn.getWritableDatabase(), conn.getSchemas());
        migration.start(conn.getWritableDatabase(), conn.getSchemas());
        migration.start(conn.getWritableDatabase(), conn.getSchemas());
    }

    @Test
    public void justRun() throws Exception {
        final AtomicInteger value = new AtomicInteger();

        OrmaDatabase orma = OrmaFactory.builder()
                .versionForManualStepMigration(100)
                .migrationStep(10, new ManualStepMigration.ChangeStep() {
                    @Override
                    public void change(@NonNull ManualStepMigration.Helper helper) {
                        value.addAndGet(10);
                    }
                })
                .migrationStep(11, new ManualStepMigration.ChangeStep() {
                    @Override
                    public void change(@NonNull ManualStepMigration.Helper helper) {
                        value.addAndGet(11);
                    }
                })
                .build();

        orma.migrate();

        assertThat("No migration on initialization", value.get(), is(0));
    }
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/OrmaConnectionTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.OrmaConnection;
import com.github.gfx.android.orma.test.model.OrmaDatabase;

import org.junit.Test;
import org.junit.runner.RunWith;

import android.content.Context;
import android.database.sqlite.SQLiteException;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

@RunWith(AndroidJUnit4.class)
public class OrmaConnectionTest {

    Context getContext() {
        return ApplicationProvider.getApplicationContext();
    }

    @Test
    public void testRawQuery() {
        OrmaDatabase db = OrmaDatabase.builder(getContext()).build();
        OrmaConnection conn = db.getConnection();
        conn.execSQL("ANALYZE");
        conn.execSQL("VACUUM");
    }


    @Test(expected = SQLiteException.class)
    public void testRawQueryWithInvalidSyntax() {
        OrmaDatabase db = OrmaDatabase.builder(getContext()).build();
        OrmaConnection conn = db.getConnection();
        conn.execSQL("foo bar baz");
    }
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/EncryptedDatabaseTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.ModelFactory;
import com.github.gfx.android.orma.test.model.Publisher;
import com.github.gfx.android.orma.test.toolbox.OrmaFactory;
import com.github.gfx.android.orma.test.toolbox.TestUtils;

import org.junit.Test;
import org.junit.runner.RunWith;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.junit.Assume.assumeTrue;

@RunWith(AndroidJUnit4.class)
public class EncryptedDatabaseTest {

    Context getContext() {
        return ApplicationProvider.getApplicationContext();
    }

    @Test
    public void simpleExecution() {
        assumeTrue(TestUtils.runOnAndroid());

        Publisher publisher = OrmaFactory.createEncrypted()
                .createPublisher(new ModelFactory<Publisher>() {
                    @NonNull
                    @Override
                    public Publisher call() {
                        Publisher publisher = new Publisher();
                        publisher.name = "<NAME>";
                        publisher.startedYear = 2015;
                        publisher.startedMonth = 12;
                        return publisher;
                    }
                });

        assertThat(publisher.id, is(not(0L)));
    }
}


<|start_filename|>processor/src/main/java/com/github/gfx/android/orma/processor/model/SchemaValidator.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.processor.model;

import com.github.gfx.android.orma.annotation.Table;
import com.github.gfx.android.orma.processor.ProcessingContext;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

public class SchemaValidator {

    final ProcessingContext context;

    final SchemaDefinition schema;

    public SchemaValidator(ProcessingContext context, SchemaDefinition schema) {
        this.context = context;
        this.schema = schema;
    }

    public static void validate(ProcessingContext context, SchemaDefinition schema) {
        new SchemaValidator(context, schema).run();
    }

    public void run() {
        validateAtLeastOneColumn();
        validatePrimaryKey();
        validateNames();
        validateNoOrmaModelInInheritance(schema.getElement().getSuperclass());
    }

    private void validateNoOrmaModelInInheritance(TypeMirror type) {
        TypeElement t = context.getTypeElement(type);
        if (t.getAnnotation(Table.class) != null) {
            error("The superclasses of Orma models are not allowed to have @Table annotation", t);
        }

        if (!t.toString().equals(Object.class.getCanonicalName())) {
            validateNoOrmaModelInInheritance(t.getSuperclass());
        }
    }

    private void validateAtLeastOneColumn() {
        if (schema.getColumns().isEmpty()) {
            error("No @Column nor @PrimaryKey is defined", schema.getElement());
        }
    }

    private void validatePrimaryKey() {
        List<ColumnDefinition> primaryKeys = schema.getColumns()
                .stream().filter(column -> column.primaryKey)
                .collect(Collectors.toList());

        if (primaryKeys.size() > 1) {
            primaryKeys.forEach(column -> {
                error("Multiple @PrimaryKey found, but it must be once", column.element);
            });
        }
    }

    private void validateNames() {
        Map<String, List<ColumnDefinition>> unique = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

        for (ColumnDefinition column : schema.getColumns()) {
            String name = column.columnName;

            List<ColumnDefinition> columns = unique.get(name);
            if (columns == null) {
                columns = new ArrayList<>();
            }
            columns.add(column);
            unique.put(name, columns);
        }

        unique.forEach((name, elements) -> {
            if (elements.size() > 1) {
                elements.forEach(column -> {
                    error("Duplicate column names \"" + name + "\" found", column.element);
                });
            }
        });
    }

    void error(String message, Element element) {
        context.addError(message, element);
    }
}


<|start_filename|>library/src/main/java/com/github/gfx/android/orma/rx/RxDataSetChangedTrigger.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.rx;

import com.github.gfx.android.orma.Schema;
import com.github.gfx.android.orma.Selector;
import com.github.gfx.android.orma.annotation.Experimental;
import com.github.gfx.android.orma.core.Database;
import com.github.gfx.android.orma.event.DataSetChangedEvent;
import com.github.gfx.android.orma.event.DataSetChangedTrigger;

import androidx.annotation.RestrictTo;

import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.subjects.PublishSubject;

/**
 * Helper class for query observables. This class is NOT thread-safe.
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Experimental
public class RxDataSetChangedTrigger implements DataSetChangedTrigger {

    // TODO: use well-tested concurrent weak maps
    final Map<WeakReference<Observer<DataSetChangedEvent<?>>>, Selector<?, ?>> observerMap = new ConcurrentHashMap<>();

    Set<Schema<?>> changedDataSetInTransaction = null;

    public <S extends Selector<?, ?>> Observable<DataSetChangedEvent<S>> create(S selector) {
        PublishSubject<DataSetChangedEvent<S>> subject = PublishSubject.create();
        register(subject, selector);
        return subject;
    }

    @SuppressWarnings("unchecked")
    public <S extends Selector<?, ?>> void register(Observer<DataSetChangedEvent<S>> observer, Selector<?, ?> selector) {
        observerMap.put(new WeakReference<>((Observer<DataSetChangedEvent<?>>) (Object) observer), selector);
    }

    @Override
    public <Model> void fire(Database db, DataSetChangedEvent.Type type, Schema<Model> schema) {
        if (observerMap.isEmpty()) {
            return;
        }
        if (db.inTransaction()) {
            addChangedDataSetInTransaction(schema);
            return;
        }

        for (Map.Entry<WeakReference<Observer<DataSetChangedEvent<?>>>, Selector<?, ?>> entry : observerMap.entrySet()) {
            Selector<?, ?> selector = entry.getValue();
            if (schema == selector.getSchema()) {
                WeakReference<Observer<DataSetChangedEvent<?>>> observerRef = entry.getKey();
                if (observerRef.get() != null) {
                    observerRef.get().onNext(new DataSetChangedEvent<>(type, selector));
                } else {
                    observerMap.remove(observerRef);
                }
            }
        }
    }

    private void addChangedDataSetInTransaction(Schema<?> schema) {
        if (changedDataSetInTransaction == null) {
            changedDataSetInTransaction = new HashSet<>();
        }

        changedDataSetInTransaction.add(schema);
    }

    @Override
    public void fireForTransaction() {
        Set<Schema<?>> schemaSet = changedDataSetInTransaction;
        changedDataSetInTransaction = null;

        if (schemaSet == null) {
            return;
        }
        for (Map.Entry<WeakReference<Observer<DataSetChangedEvent<?>>>, Selector<?, ?>> entry : observerMap.entrySet()) {
            Selector<?, ?> selector = entry.getValue();
            if (schemaSet.contains(selector.getSchema())) {
                WeakReference<Observer<DataSetChangedEvent<?>>> observerRef = entry.getKey();
                if (observerRef.get() != null) {
                    observerRef.get().onNext(new DataSetChangedEvent<>(DataSetChangedEvent.Type.TRANSACTION, selector));
                } else {
                    observerMap.remove(observerRef);
                }
            }
        }
    }
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/ParcelableTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.ModelFactory;
import com.github.gfx.android.orma.SingleAssociation;
import com.github.gfx.android.orma.test.model.ModelParcelable;
import com.github.gfx.android.orma.test.model.OrmaDatabase;
import com.github.gfx.android.orma.test.toolbox.OrmaFactory;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

@RunWith(AndroidJUnit4.class)
public class ParcelableTest {

    OrmaDatabase orma;

    Context getContext() {
        return ApplicationProvider.getApplicationContext();
    }

    <T extends Parcelable> T reload(T parcelable) {
        Parcel parcel = Parcel.obtain();

        parcel.writeParcelable(parcelable, 0);
        parcel.setDataPosition(0);

        T reloadedParcelable = parcel.readParcelable(getContext().getClassLoader());

        parcel.recycle();

        return reloadedParcelable;
    }

    @Before
    public void setUp() throws Exception {
        orma = OrmaFactory.create();
    }

    @Test
    public void testSingleAssociation() throws Exception {
        ModelParcelable model = orma.createModelParcelable(new ModelFactory<ModelParcelable>() {
            @NonNull
            @Override
            public ModelParcelable call() {
                return new ModelParcelable(0, "foo");
            }
        });

        SingleAssociation<ModelParcelable> ref = reload(SingleAssociation.just(model));

        assertThat(ref.getId(), is(model.id));
        assertThat(ref.get().text, is(model.text));
    }
}


<|start_filename|>processor/src/main/java/com/github/gfx/android/orma/processor/generator/DatabaseWriter.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.processor.generator;

import com.github.gfx.android.orma.annotation.OnConflict;
import com.github.gfx.android.orma.processor.OrmaProcessor;
import com.github.gfx.android.orma.processor.ProcessingContext;
import com.github.gfx.android.orma.processor.model.DatabaseDefinition;
import com.github.gfx.android.orma.processor.model.SchemaDefinition;
import com.github.gfx.android.orma.processor.util.Annotations;
import com.github.gfx.android.orma.processor.util.Types;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;

public class DatabaseWriter extends BaseWriter {

    static final String kBuilderClassName = "Builder";

    static final Modifier[] publicStaticFinal = {
            Modifier.PUBLIC,
            Modifier.STATIC,
            Modifier.FINAL,
    };

    static final String connection = "connection";

    static final String SCHEMAS = "SCHEMAS";

    final DatabaseDefinition database;

    final ClassName ormaConnectionType;

    public DatabaseWriter(ProcessingContext context, DatabaseDefinition database) {
        super(context);
        this.database = database;
        this.ormaConnectionType = database.isRxJavaSupport() ? Types.RxOrmaConnection : Types.OrmaConnection;
    }

    // http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java
    public static String bytesToHex(byte[] bytes) {
        final char[] HEX_TABLE = "0123456789ABCDEF".toCharArray();

        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_TABLE[v >>> 4];
            hexChars[j * 2 + 1] = HEX_TABLE[v & 0x0F];
        }
        return new String(hexChars);
    }

    @Override
    public String getPackageName() {
        return database.getPackageName();
    }

    @Override
    public Optional<? extends Element> getElement() {
        return database.getElement();
    }

    public ClassName getClassName() {
        return database.getClassName();
    }

    @Override
    public TypeSpec buildTypeSpec() {
        ClassName builderClass = ClassName.get(getPackageName(), getClassName().simpleName(), kBuilderClassName);

        TypeSpec.Builder classBuilder = TypeSpec.classBuilder(getClassName());
        classBuilder.addJavadoc("<p>The Orma database handle class.</p>\n");
        classBuilder.addJavadoc("<p>To create instances: {@code $T.builder(context).build()}.</p>\n", getClassName());
        classBuilder.addJavadoc(
                "<p>To delete databases: {@code context.deleteDatabase($T.Builder.getDefaultDatabaseName(context))}</p>\n\n",
                getClassName());

        database.getElement()
                .map(ClassName::get)
                .ifPresent(databaseConfigClass ->
                        classBuilder.addJavadoc("<p>This is configured in {@link $T}</p>\n", databaseConfigClass));

        classBuilder.addJavadoc("<p>This is generated by {@code $L}.</p>\n", OrmaProcessor.class.getCanonicalName());
        classBuilder.addModifiers(Modifier.PUBLIC);
        classBuilder.addSuperinterface(Types.DatabaseHandle);

        classBuilder.addType(buildBuilderTypeSpec(builderClass));

        classBuilder.addFields(buildFieldSpecs());
        classBuilder.addMethods(buildMethodSpecs(builderClass));

        return classBuilder.build();
    }

    private TypeSpec buildBuilderTypeSpec(ClassName builderClass) {
        TypeSpec.Builder builder = TypeSpec.classBuilder(builderClass);

        builder.addModifiers(Modifier.PUBLIC, Modifier.STATIC);

        builder.superclass(ParameterizedTypeName.get(Types.OrmaDatabaseBuilderBase, builderClass));

        builder.addMethod(MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ParameterSpec.builder(Types.Context, "context")
                        .addAnnotation(Annotations.nonNull())
                        .build())
                .addStatement("super(context)")
                .build());

        builder.addMethod(
                MethodSpec.methodBuilder("getSchemaHash")
                        .addModifiers(Modifier.PROTECTED)
                        .addAnnotations(Annotations.overrideAndNonNull())
                        .returns(Types.String)
                        .addStatement("return SCHEMA_HASH")
                        .build()
        );

        builder.addMethod(MethodSpec.methodBuilder("build")
                .addModifiers(Modifier.PUBLIC)
                .returns(getClassName())
                .addStatement("return new $T(new $T(fillDefaults(), $L))", getClassName(), ormaConnectionType, SCHEMAS)
                .build());

        return builder.build();
    }

    public List<FieldSpec> buildFieldSpecs() {
        List<FieldSpec> fieldSpecs = new ArrayList<>();

        fieldSpecs.add(
                FieldSpec.builder(String.class, "SCHEMA_HASH", Modifier.PUBLIC, Modifier.STATIC)
                        .addJavadoc(
                                "The SHA-256 digest of all the {@code CREATE TABLE} and {@code CREATE INDEX} statements.\n")
                        .initializer("$S", buildSchemaSha256())
                        .build()
        );

        fieldSpecs.add(
                FieldSpec.builder(Types.getList(Types.WildcardSchema), SCHEMAS, publicStaticFinal)
                        .initializer(buildSchemasInitializer())
                        .build());

        fieldSpecs.add(
                FieldSpec.builder(ormaConnectionType, connection, Modifier.PRIVATE, Modifier.FINAL)
                        .build());

        return fieldSpecs;
    }

    private String buildSchemaSha256() {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("never reached", e);
        }
        Charset utf8 = Charset.forName("UTF-8");

        database.getSchemas().forEach(schema -> {
            md.update(schema.getCreateTableStatement().getBytes(utf8));
            schema.getCreateIndexStatements().forEach(statement -> md.update(statement.getBytes(utf8)));
        });

        return bytesToHex(md.digest());
    }

    private CodeBlock buildSchemasInitializer() {
        CodeBlock.Builder builder = CodeBlock.builder();
        builder.add("$T.<$T>asList(\n", Types.Arrays, Types.WildcardSchema).indent();

        List<SchemaDefinition> schemas = database.getSchemas();

        for (int i = 0; i < schemas.size(); i++) {
            builder.add(schemas.get(i).createSchemaInstanceExpr());

            if ((i + 1) != schemas.size()) {
                builder.add(",\n");
            } else {
                builder.add("\n");
            }
        }

        builder.unindent().add(")");
        return builder.build();
    }

    public List<MethodSpec> buildMethodSpecs(ClassName builderClass) {
        List<MethodSpec> methodSpecs = new ArrayList<>();

        methodSpecs.addAll(buildConstructorSpecs());

        methodSpecs.add(
                MethodSpec.methodBuilder("builder")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .returns(builderClass)
                        .addParameter(ParameterSpec.builder(Types.Context, "context")
                                .addAnnotation(Annotations.nonNull())
                                .build())
                        .addStatement("return new $T(context)", builderClass)
                        .build());

        methodSpecs.add(
                MethodSpec.methodBuilder("getSchemas")
                        .addAnnotations(Annotations.overrideAndNonNull())
                        .addModifiers(Modifier.PUBLIC)
                        .returns(Types.getList(Types.WildcardSchema))
                        .addStatement("return $L", SCHEMAS)
                        .build()
        );

        methodSpecs.add(
                MethodSpec.methodBuilder("migrate")
                        .addJavadoc("<p>{@code migrate()} invokes database migration, which will takes several seconds.</p>\n"
                                + "<p>This is completely optional and migration is invoked on the first access of the database, anyway.</p>\n\n"
                                + "@throws SQLiteConstraintException migration information is not sufficient.\n")
                        .addModifiers(Modifier.PUBLIC)
                        .addException(Types.SQLiteConstraintException)
                        .addStatement("$L.getWritableDatabase()", connection)
                        .build()
        );

        methodSpecs.add(
                MethodSpec.methodBuilder("getConnection")
                        .addAnnotations(Annotations.overrideAndNonNull())
                        .addModifiers(Modifier.PUBLIC)
                        .returns(ormaConnectionType)
                        .addStatement("return $L", connection)
                        .build()
        );

        methodSpecs.add(
                MethodSpec.methodBuilder("transactionSync")
                        .addAnnotation(Annotations.workerThread())
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(
                                ParameterSpec.builder(Types.Runnable, "task")
                                        .addAnnotation(Annotations.nonNull())
                                        .build())
                        .addStatement("$L.transactionSync(task)", connection)
                        .build()
        );

        methodSpecs.add(
                MethodSpec.methodBuilder("transactionNonExclusiveSync")
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(
                                ParameterSpec.builder(Types.Runnable, "task")
                                        .addAnnotation(Annotations.nonNull())
                                        .build())
                        .addStatement("$L.transactionNonExclusiveSync(task)", connection)
                        .build()
        );

        if (database.isRxJavaSupport()) {
            methodSpecs.add(
                    MethodSpec.methodBuilder("transactionAsCompletable")
                            .addJavadoc("RxJava 2.x {@code Completable} wrapper to {@link #transactionSync(Runnable)}\n")
                            .addAnnotation(Annotations.checkResult())
                            .addModifiers(Modifier.PUBLIC)
                            .returns(Types.Completable)
                            .addParameter(
                                    ParameterSpec.builder(Types.Runnable, "task")
                                            .addAnnotation(Annotations.nonNull())
                                            .addModifiers(Modifier.FINAL)
                                            .build())
                            .addStatement("return $T.fromRunnable($L)", Types.Completable,
                                    runnableWithCode("transactionSync(task)"))
                            .build()
            );

            methodSpecs.add(
                    MethodSpec.methodBuilder("transactionNonExclusiveAsCompletable")
                            .addJavadoc(
                                    "RxJava 2.x {@code Completable} wrapper to {@link #transactionNonExclusiveSync(Runnable)}\n")
                            .addAnnotation(Annotations.checkResult())
                            .addModifiers(Modifier.PUBLIC)
                            .returns(Types.Completable)
                            .addParameter(
                                    ParameterSpec.builder(Types.Runnable, "task")
                                            .addAnnotation(Annotations.nonNull())
                                            .addModifiers(Modifier.FINAL)
                                            .build())
                            .addStatement("return $T.fromRunnable($L)", Types.Completable,
                                    runnableWithCode("transactionNonExclusiveSync(task)"))
                            .build()
            );
        }

        methodSpecs.add(
                MethodSpec.methodBuilder("deleteAll")
                        .addModifiers(Modifier.PUBLIC)
                        .addStatement("$L.deleteAll()", connection)
                        .build()
        );

        database.getSchemas().forEach(schema -> {
            String simpleModelName = schema.getModelClassName().simpleName();
            CodeBlock schemaInstance = schema.createSchemaInstanceExpr();

            List<AnnotationSpec> suppressWarningsRawtypes = schema.isGeneric()
                    ? Collections.singletonList(Annotations.suppressWarnings("rawtypes"))
                    : Collections.emptyList();

            methodSpecs.add(MethodSpec.methodBuilder("new" + simpleModelName + "FromCursor")
                    .addJavadoc("Retrieves a model from a cursor.")
                    .addAnnotation(Annotations.nonNull())
                    .addAnnotations(suppressWarningsRawtypes)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(schema.getModelClassName())
                    .addParameter(
                            ParameterSpec.builder(Types.Cursor, "cursor")
                                    .addAnnotation(Annotations.nonNull())
                                    .build()
                    )
                    .addStatement("return $L.newModelFromCursor($L, cursor, 0)", schemaInstance, connection)
                    .build());

            methodSpecs.add(MethodSpec.methodBuilder("create" + simpleModelName)
                    .addJavadoc(
                            "Inserts a model created by {@code ModelFactory<T>},"
                                    + " and retrieves it which is just inserted.\n"
                                    + " The return value has the row ID.\n")
                    .addAnnotation(Annotations.nonNull())
                    .addAnnotation(Annotations.workerThread())
                    .addAnnotations(suppressWarningsRawtypes)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(schema.getModelClassName())
                    .addParameter(
                            ParameterSpec.builder(Types.getModelFactory(schema.getModelClassName()), "factory")
                                    .addAnnotation(Annotations.nonNull())
                                    .build()
                    )
                    .addStatement("return $L.createModel($L, factory)", connection, schemaInstance)
                    .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("relationOf" + simpleModelName)
                            .addJavadoc("Creates a relation of {@code $T}, which is an entry point of all the operations.\n",
                                    schema.getModelClassName())
                            .addAnnotation(Annotations.nonNull())
                            .addModifiers(Modifier.PUBLIC)
                            .returns(schema.getRelationClassName())
                            .addStatement("return new $T($L, $L)",
                                    schema.getRelationClassName(),
                                    connection,
                                    schemaInstance)
                            .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("selectFrom" + simpleModelName)
                            .addJavadoc("Starts building a query: {@code SELECT * FROM $T ...}.\n", schema.getModelClassName())
                            .addAnnotation(Annotations.nonNull())
                            .addModifiers(Modifier.PUBLIC)
                            .returns(schema.getSelectorClassName())
                            .addStatement("return new $T($L, $L)",
                                    schema.getSelectorClassName(),
                                    connection,
                                    schemaInstance)
                            .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("update" + simpleModelName)
                            .addJavadoc("Starts building a query: {@code UPDATE $T ...}.\n", schema.getModelClassName())
                            .addAnnotation(Annotations.workerThread())
                            .addAnnotation(Annotations.nonNull())
                            .addModifiers(Modifier.PUBLIC)
                            .returns(schema.getUpdaterClassName())
                            .addStatement("return new $T($L, $L)",
                                    schema.getUpdaterClassName(),
                                    connection,
                                    schemaInstance)
                            .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("deleteFrom" + simpleModelName)
                            .addJavadoc("Starts building a query: {@code DELETE FROM $T ...}.\n", schema.getModelClassName())
                            .addAnnotation(Annotations.workerThread())
                            .addAnnotation(Annotations.nonNull())
                            .addModifiers(Modifier.PUBLIC)
                            .returns(schema.getDeleterClassName())
                            .addStatement("return new $T($L, $L)",
                                    schema.getDeleterClassName(),
                                    connection,
                                    schemaInstance)
                            .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("insertInto" + simpleModelName)
                            .addJavadoc("Executes a query: {@code INSERT INTO $T ...}.\n", schema.getModelClassName())
                            .addAnnotation(Annotations.workerThread())
                            .addAnnotations(suppressWarningsRawtypes)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(long.class)
                            .addParameter(
                                    ParameterSpec.builder(schema.getModelClassName(), "model")
                                            .addAnnotation(Annotations.nonNull())
                                            .build()
                            )
                            .addStatement("return prepareInsertInto$L().executeAndClose(model)",
                                    simpleModelName
                            )
                            .build());

            // For prepared statements
            ParameterizedTypeName inserterType = database.isRxJavaSupport()
                    ? Types.getRxInserter(schema.getModelClassName())
                    : Types.getInserter(schema.getModelClassName());

            methodSpecs.add(
                    MethodSpec.methodBuilder("prepareInsertInto" + simpleModelName)
                            .addJavadoc("Create a prepared statement for {@code INSERT INTO $T ...}.\n",
                                    schema.getModelClassName())
                            .addAnnotation(Annotations.workerThread())
                            .addAnnotations(suppressWarningsRawtypes)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(inserterType)
                            .addStatement("return prepareInsertInto$L($T.NONE, true)",
                                    simpleModelName,
                                    OnConflict.class
                            )
                            .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("prepareInsertInto" + simpleModelName)
                            .addJavadoc("Create a prepared statement for {@code INSERT OR ... INTO $T ...}.\n",
                                    schema.getModelClassName())
                            .addAnnotation(Annotations.workerThread())
                            .addAnnotations(suppressWarningsRawtypes)
                            .addModifiers(Modifier.PUBLIC)
                            .addParameter(ParameterSpec.builder(int.class, "onConflictAlgorithm")
                                    .addAnnotation(OnConflict.class)
                                    .build())
                            .returns(inserterType)
                            .addStatement("return prepareInsertInto$L(onConflictAlgorithm, true)",
                                    simpleModelName
                            )
                            .build());

            methodSpecs.add(
                    MethodSpec.methodBuilder("prepareInsertInto" + simpleModelName)
                            .addJavadoc("Create a prepared statement for {@code INSERT OR ... INTO $T ...}.\n",
                                    schema.getModelClassName())
                            .addAnnotation(Annotations.workerThread())
                            .addAnnotations(suppressWarningsRawtypes)
                            .addModifiers(Modifier.PUBLIC)
                            .addParameter(ParameterSpec.builder(int.class, "onConflictAlgorithm")
                                    .addAnnotation(OnConflict.class)
                                    .build())
                            .addParameter(ParameterSpec.builder(boolean.class, "withoutAutoId")
                                    .build())
                            .returns(inserterType)
                            .addStatement("return new $T($L, $L, onConflictAlgorithm, withoutAutoId)",
                                    inserterType,
                                    connection,
                                    schemaInstance
                            )
                            .build());

            // For prepared statements RxJava observables
            if (database.isRxJavaSupport()) {
                // RxJava 2.x
                TypeName inserterSingle2Type = Types.getSingle(inserterType);

                methodSpecs.add(
                        MethodSpec.methodBuilder("prepareInsertInto" + simpleModelName + "AsSingle")
                                .addJavadoc("Create a prepared statement for {@code INSERT INTO $T ...}.\n",
                                        schema.getModelClassName())
                                .addAnnotation(Annotations.checkResult())
                                .addAnnotations(suppressWarningsRawtypes)
                                .addModifiers(Modifier.PUBLIC)
                                .returns(inserterSingle2Type)
                                .addStatement("return prepareInsertInto$LAsSingle($T.NONE, true)",
                                        simpleModelName,
                                        OnConflict.class
                                )
                                .build());

                methodSpecs.add(
                        MethodSpec.methodBuilder("prepareInsertInto" + simpleModelName + "AsSingle")
                                .addJavadoc("Create a prepared statement for {@code INSERT OR ... INTO $T ...}.\n",
                                        schema.getModelClassName())
                                .addAnnotation(Annotations.checkResult())
                                .addAnnotations(suppressWarningsRawtypes)
                                .addModifiers(Modifier.PUBLIC)
                                .addParameter(ParameterSpec.builder(int.class, "onConflictAlgorithm")
                                        .addAnnotation(OnConflict.class)
                                        .build())
                                .returns(inserterSingle2Type)
                                .addStatement("return prepareInsertInto$LAsSingle(onConflictAlgorithm, true)",
                                        simpleModelName
                                )
                                .build());
                methodSpecs.add(
                        MethodSpec.methodBuilder("prepareInsertInto" + simpleModelName + "AsSingle")
                                .addJavadoc("Create a prepared statement for {@code INSERT OR ... INTO $T ...}.\n",
                                        schema.getModelClassName())
                                .addAnnotation(Annotations.checkResult())
                                .addAnnotations(suppressWarningsRawtypes)
                                .addModifiers(Modifier.PUBLIC)
                                .addParameter(ParameterSpec.builder(int.class, "onConflictAlgorithm")
                                        .addModifiers(Modifier.FINAL)
                                        .addAnnotation(OnConflict.class)
                                        .build())
                                .addParameter(ParameterSpec.builder(boolean.class, "withoutAutoId")
                                        .addModifiers(Modifier.FINAL)
                                        .build())
                                .returns(inserterSingle2Type)
                                .addStatement("return $T.fromCallable($L)", Types.Single,
                                        callableWithCode(inserterType,
                                                "return new $T($L, $L, onConflictAlgorithm, withoutAutoId)",
                                                inserterType, connection, schemaInstance))
                                .build());

            }
        });

        return methodSpecs;
    }

    TypeSpec runnableWithCode(String statement, Object... args) {
        return TypeSpec.anonymousClassBuilder("")
                .superclass(Types.Runnable)
                .addMethod(MethodSpec.methodBuilder("run")
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(Annotations.override())
                        .addStatement(statement, args)
                        .build())
                .build();
    }

    TypeSpec callableWithCode(TypeName returnType, String statement, Object... args) {
        return TypeSpec.anonymousClassBuilder("")
                .superclass(ParameterizedTypeName.get(Types.Callable, returnType))
                .addMethod(MethodSpec.methodBuilder("call")
                        .addAnnotation(Annotations.override())
                        .addModifiers(Modifier.PUBLIC)
                        .returns(returnType)
                        .addException(Exception.class)
                        .addStatement(statement, args)
                        .build())
                .build();
    }

    public List<MethodSpec> buildConstructorSpecs() {
        List<MethodSpec> methodSpecs = new ArrayList<>();

        methodSpecs.add(MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(
                        ParameterSpec.builder(ormaConnectionType, connection)
                                .addAnnotation(Annotations.nonNull())
                                .build())
                .addStatement("this.$L = $L", connection, connection)
                .build());

        return methodSpecs;
    }
}


<|start_filename|>library/src/main/java/com/github/gfx/android/orma/internal/TypeHolder.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.internal;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

/**
 * A helper class to hold the type instance of parameterized types.
 *
 * @see <a href="https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/reflect/TypeToken.java">google/gson/TypeToken.java</a>
 */
public abstract class TypeHolder<T> {

    public Type getType() {
        @SuppressWarnings("unchecked")
        Class<TypeHolder<T>> c = (Class<TypeHolder<T>>) getClass();
        ParameterizedType t;
        try {
            t = (ParameterizedType) c.getGenericSuperclass();
        } catch (ClassCastException e) {
            throw new RuntimeException("No type signature found. Missing -keepattributes Signature in progurad-rules.pro?", e);
        }
        return EquatableTypeWrapper.wrap(t.getActualTypeArguments()[0]);
    }

    private static class EquatableTypeWrapper implements ParameterizedType {

        static final boolean JVM_TESTING = !System.getProperty("java.vm.name").equals("Dalvik");

        static final boolean USE_TYPE_WRAPPER;

        static {
            Type a = new TypeHolder<List<String>>() {
            }.getType();
            Type b = new TypeHolder<List<String>>() {
            }.getType();

            // ParameterizedType implementation differs in runtime environments:
            // sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl (Oracle JDK 8)
            // org.apache.harmony.luni.lang.reflect.ImplForType (Android 4.2.2)
            // libcore.reflect.ParameterizedTypeImpl (Android 5.0.2)
            // Anyway, always use EquatableTypeWrapper in JVM testing.
            USE_TYPE_WRAPPER = JVM_TESTING || !a.equals(b);
        }

        final ParameterizedType type;

        public EquatableTypeWrapper(ParameterizedType type) {
            this.type = type;
        }

        public static Type wrap(Type type) {
            if (USE_TYPE_WRAPPER && type instanceof ParameterizedType) {
                return new EquatableTypeWrapper((ParameterizedType) type);
            } else {
                return type;
            }
        }

        @Override
        public int hashCode() {
            return type.toString().hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof EquatableTypeWrapper)) {
                return false;
            }
            EquatableTypeWrapper that = (EquatableTypeWrapper) o;
            return type.getRawType().equals(that.type.getRawType())
                    && Arrays.deepEquals(type.getActualTypeArguments(), that.type.getActualTypeArguments());
        }

        @Override
        public String toString() {
            return type.toString();
        }

        @Override
        public Type[] getActualTypeArguments() {
            return type.getActualTypeArguments();
        }

        @Override
        public Type getOwnerType() {
            return type.getOwnerType();
        }

        @Override
        public Type getRawType() {
            return type.getRawType();
        }
    }
}


<|start_filename|>migration/src/main/java/com/github/gfx/android/orma/migration/SchemaDiffMigration.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.migration;

import com.github.gfx.android.orma.core.Database;
import com.github.gfx.android.orma.migration.sqliteparser.CreateIndexStatement;
import com.github.gfx.android.orma.migration.sqliteparser.CreateTableStatement;
import com.github.gfx.android.orma.migration.sqliteparser.SQLiteComponent;
import com.github.gfx.android.orma.migration.sqliteparser.SQLiteParserUtils;

import org.json.JSONArray;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Pair;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

@SuppressLint("Assert")
public class SchemaDiffMigration extends AbstractMigrationEngine {

    public static final String TAG = "SchemaDiffMigration";

    public static final String MIGRATION_STEPS_TABLE_1 = "orma_schema_diff_migration_steps";

    public static final String MIGRATION_STEPS_TABLE = "orma_schema_diff_migration_2";

    static final String kId = "id";

    static final String kDbVersion = "db_version"; // PRAGMA schema_version

    static final String kVersionName = "version_name";

    static final String kVersionCode = "version_code";

    static final String kSchemaHash = "schema_hash";

    static final String kSql = "sql";

    static final String kArgs = "args";

    static final String kCreatedTimestamp = "created_timestamp";

    public static final String SCHEMA_DIFF_DDL = "CREATE TABLE IF NOT EXISTS "
            + MIGRATION_STEPS_TABLE + " ("
            + kId + " INTEGER PRIMARY KEY AUTOINCREMENT, "
            + kDbVersion + " INTEGER NOT NULL, "
            + kVersionName + " TEXT NOT NULL, "
            + kVersionCode + " INTEGER NOT NULL, "
            + kSchemaHash + " TEXT NOT NULL, "
            + kSql + " TEXT NULL, "
            + kArgs + " TEXT NULL, "
            + kCreatedTimestamp + " DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP)";

    final String versionName;

    final int versionCode;

    final String schemaHash;

    final SqliteDdlBuilder util = new SqliteDdlBuilder();

    private boolean tableCreated = false;

    public SchemaDiffMigration(@NonNull Context context, @NonNull String schemaHash, @NonNull TraceListener traceListener) {
        super(traceListener);
        this.versionName = extractVersionName(context);
        this.versionCode = extractVersionCode(context);
        this.schemaHash = schemaHash;
    }

    public SchemaDiffMigration(@NonNull Context context, @NonNull String schemaHash) {
        this(context, schemaHash, extractDebuggable(context) ? TraceListener.LOGCAT : TraceListener.EMPTY);
    }

    static private Map<CreateIndexStatement, String> parseIndexes(@NonNull Collection<String> indexes) {
        Map<CreateIndexStatement, String> parsedIndexPairs = new LinkedHashMap<>();
        for (String createIndexStatement : indexes) {
            parsedIndexPairs.put(SQLiteParserUtils.parseIntoCreateIndexStatement(createIndexStatement), createIndexStatement);
        }
        return parsedIndexPairs;
    }

    private static String serializeArgs(@NonNull Object[] args) {
        if (args.length == 0) {
            return "[]";
        }

        JSONArray array = new JSONArray();
        for (Object arg : args) {
            array.put(arg);
        }
        return array.toString();
    }

    public static Map<String, SQLiteMaster> loadMetadata(@NonNull Database db, List<? extends MigrationSchema> schemas) {
        Map<String, SQLiteMaster> metadata = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

        Set<String> tableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
        for (MigrationSchema schema : schemas) {
            tableNames.add(schema.getTableName());
        }

        for (Map.Entry<String, SQLiteMaster> entry : SQLiteMaster.loadTables(db).entrySet()) {
            if (tableNames.contains(entry.getKey())) {
                metadata.put(entry.getKey(), entry.getValue());
            }
        }
        return metadata;
    }

    @NonNull
    @Override
    public String getTag() {
        return TAG;
    }

    @Override
    public void start(@NonNull Database db, @NonNull List<? extends MigrationSchema> schemas) {
        if (isSchemaChanged(db)) {
            Map<String, ? extends MigrationSchema> masterSchemas = loadMetadata(db, schemas);
            List<String> statements = diffAll(masterSchemas, schemas);
            if (statements.isEmpty()) {
                int dbVersion = fetchDbSchemaVersion(db);
                saveStep(db, dbVersion, null); // just to save the db version
            } else {
                executeStatements(db, statements);
            }
        }
    }

    public boolean isSchemaChanged(@NonNull Database db) {
        Pair<Integer, String> versions = fetchSchemaVersions(db);
        int dbVersion = fetchDbSchemaVersion(db);
        return !(dbVersion == versions.first && schemaHash.equals(versions.second));
    }

    @NonNull
    private Pair<Integer, String> fetchSchemaVersions(@NonNull Database db) {
        ensureHistoryTableExists(db);
        Cursor cursor = db.query(MIGRATION_STEPS_TABLE, new String[]{kDbVersion, kSchemaHash},
                null, null, null, null, kId + " DESC", "1");
        try {
            if (cursor.moveToFirst()) {
                return new Pair<>(cursor.getInt(0), cursor.getString(1));
            } else {
                return new Pair<>(0, "");
            }
        } finally {
            cursor.close();
        }
    }

    void ensureHistoryTableExists(@NonNull Database db) {
        if (!tableCreated) {
            migrate(db);
            tableCreated = true;
        }
    }

    public void migrate(@NonNull Database db) {
        if (SQLiteMaster.checkIfTableNameExists(db, MIGRATION_STEPS_TABLE_1)) {
            try {
                db.beginTransaction();

                db.execSQL(SCHEMA_DIFF_DDL);
                String[] src = {kId, "0", kVersionName, kVersionCode, kSchemaHash, kSql, kArgs, kCreatedTimestamp};
                String[] dst = {kId, kDbVersion, kVersionName, kVersionCode, kSchemaHash, kSql, kArgs, kCreatedTimestamp};
                db.execSQL("INSERT INTO " + MIGRATION_STEPS_TABLE + " ("
                        + TextUtils.join(", ", dst)
                        + ") SELECT "
                        + TextUtils.join(", ", src) + " FROM " + MIGRATION_STEPS_TABLE_1);
                db.execSQL("DROP TABLE " + MIGRATION_STEPS_TABLE_1);
                db.execSQL(SCHEMA_DIFF_DDL);

                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        } else {
            db.execSQL(SCHEMA_DIFF_DDL);
        }
    }

    @NonNull
    public List<String> diffAll(@NonNull Map<String, ? extends MigrationSchema> srsSchemas,
            @NonNull List<? extends MigrationSchema> dstSchemas) {
        List<String> statements = new ArrayList<>();

        // NOTE: ignore tables which exist only in database
        for (MigrationSchema dstSchema : dstSchemas) {
            MigrationSchema srcSchema = srsSchemas.get(dstSchema.getTableName());
            if (srcSchema == null) {
                statements.add(dstSchema.getCreateTableStatement());
                statements.addAll(dstSchema.getCreateIndexStatements());
            } else {
                List<String> tableDiffStatements = tableDiff(srcSchema.getCreateTableStatement(),
                        dstSchema.getCreateTableStatement());

                if (tableDiffStatements.isEmpty()) {
                    statements.addAll(indexDiff(srcSchema.getCreateIndexStatements(), dstSchema.getCreateIndexStatements()));
                } else {
                    // This table needs re-create, where all the indexes are also dropped.
                    statements.addAll(tableDiffStatements);
                    statements.addAll(dstSchema.getCreateIndexStatements());
                }
            }
        }
        return statements;
    }

    /**
     * @param srcIndexes Set of "CREATE INDEX" statements which the DB has
     * @param dstIndexes Set of "CREATE INDEX" statements which the running code has
     * @return List of "CREATED INDEX" statements to apply to DB
     */
    @NonNull
    public List<String> indexDiff(@NonNull Collection<String> srcIndexes, @NonNull Collection<String> dstIndexes) {
        LinkedHashMap<CreateIndexStatement, String> unionIndexes = new LinkedHashMap<>();

        Map<CreateIndexStatement, String> srcIndexesPairs = parseIndexes(srcIndexes);
        unionIndexes.putAll(srcIndexesPairs);

        Map<CreateIndexStatement, String> dstIndexesPairs = parseIndexes(dstIndexes);
        unionIndexes.putAll(dstIndexesPairs);

        List<String> createIndexStatements = new ArrayList<>();
        for (Map.Entry<CreateIndexStatement, String> createIndexStatement : unionIndexes.entrySet()) {
            boolean existsInDst = dstIndexesPairs.containsKey(createIndexStatement.getKey());
            boolean existsInSrc = srcIndexesPairs.containsKey(createIndexStatement.getKey());

            if (existsInDst && existsInSrc) {
                // okay, nothing to do
            } else if (existsInDst) {
                createIndexStatements.add(createIndexStatement.getValue());
            } else { // existsInSrc
                createIndexStatements.add(buildDropIndexStatement(createIndexStatement.getKey()));
            }
        }
        return createIndexStatements;
    }

    @NonNull
    public List<String> tableDiff(@NonNull String from, @NonNull String to) {
        if (from.equals(to)) {
            return Collections.emptyList();
        }

        CreateTableStatement fromTable = SQLiteParserUtils.parseIntoCreateTableStatement(from);
        CreateTableStatement toTable = SQLiteParserUtils.parseIntoCreateTableStatement(to);

        Set<CreateTableStatement.ColumnDef> toColumns = new LinkedHashSet<>();
        Set<SQLiteComponent.Name> toColumnNames = new LinkedHashSet<>();
        List<CreateTableStatement.ColumnDef> intersectionColumns = new ArrayList<>();

        List<SQLiteComponent.Name> intersectionColumnNames = new ArrayList<>();

        for (CreateTableStatement.ColumnDef column : toTable.getColumns()) {
            toColumns.add(column);
            toColumnNames.add(column.getName());
        }

        for (CreateTableStatement.ColumnDef column : fromTable.getColumns()) {
            if (toColumns.contains(column)) {
                intersectionColumns.add(column);
            }

            if (toColumnNames.contains(column.getName())) {
                intersectionColumnNames.add(column.getName());
            }
        }

        if (intersectionColumns.size() != toTable.getColumns().size() ||
                intersectionColumns.size() != fromTable.getColumns().size() ||
                !fromTable.getConstraints().equals(toTable.getConstraints())) {
            trace("from: %s", from);
            trace("to:   %s", to);
            return util.buildRecreateTable(fromTable, toTable, intersectionColumnNames, intersectionColumnNames);
        } else {
            return Collections.emptyList();
        }
    }

    @NonNull
    public String buildDropIndexStatement(@NonNull String createIndexStatement) {
        return buildDropIndexStatement(SQLiteParserUtils.parseIntoCreateIndexStatement(createIndexStatement));
    }

    @NonNull
    public String buildDropIndexStatement(@NonNull CreateIndexStatement createIndexStatement) {
        return "DROP INDEX IF EXISTS " + createIndexStatement.getIndexName();
    }

    public void saveStep(@NonNull Database db, int dbVersion, @Nullable String sql, @NonNull Object... args) {
        ensureHistoryTableExists(db);

        ContentValues values = new ContentValues();
        values.put(kDbVersion, dbVersion);
        values.put(kVersionName, versionName);
        values.put(kVersionCode, versionCode);
        values.put(kSchemaHash, schemaHash);
        values.put(kSql, sql);
        values.put(kArgs, serializeArgs(args));
        db.insertOrThrow(MIGRATION_STEPS_TABLE, null, values);
    }

    public void executeStatements(@NonNull final Database db, @NonNull final List<String> statements) {
        if (statements.isEmpty()) {
            return;
        }

        transaction(db, new Runnable() {
            @Override
            public void run() {
                for (String statement : statements) {
                    trace("%s", statement);
                    db.execSQL(statement);
                }

                int dbVersion = fetchDbSchemaVersion(db);
                for (String statement : statements) {
                    saveStep(db, dbVersion, statement);
                }
            }
        });
    }

    // "SQLite automatically increments the schema-version whenever the schema changes."
    // as described in https://www.sqlite.org/pragma.html#pragma_schema_version
    private static int fetchDbSchemaVersion(@NonNull Database db) {
        return (int) db.longForQuery("PRAGMA schema_version", null);
    }
}


<|start_filename|>example/src/main/java/com/github/gfx/android/orma/example/handwritten/HandWrittenOpenHelper.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.example.handwritten;

import com.github.gfx.android.orma.core.Database;
import com.github.gfx.android.orma.core.DatabaseProvider;

import android.content.Context;

public class HandWrittenOpenHelper {

    static final int VERSION = 4;

    private final Context context;

    private final String name;

    private final DatabaseProvider provider;

    private Database db;

    public HandWrittenOpenHelper(Context context, String name, DatabaseProvider provider) {
        this.context = context;
        this.name = name;
        this.provider = provider;
    }

    public Database getWritableDatabase() {
        if (db == null) {
            db = provider.provideOnDiskDatabase(context, name, 0);
            onCreate(db);
        }
        return db;
    }

    public Database getReadableDatabase() {
        return getWritableDatabase();
    }

    private void onCreate(Database db) {
        db.execSQL("CREATE TABLE todo ("
                + "id INTEGER PRIMARY KEY,"
                + "title TEXT NOT NULL,"
                + "content TEXT NULL,"
                + "done BOOLEAN NOT NULL,"
                + "createdTime INTEGER NOT NULL"
                + ")");
        db.execSQL("CREATE INDEX title_on_todo ON todo (title)");
        db.execSQL("CREATE INDEX createdTimeMillis_on_todo ON todo (createdTime)");
    }
}


<|start_filename|>migration/src/test/java/com/github/gfx/android/orma/migration/test/util/SchemaData.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.migration.test.util;

import com.github.gfx.android.orma.migration.MigrationSchema;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SchemaData implements MigrationSchema {

    final String tableName;

    final String createTableStatement;

    final List<String> createIndexStatements;

    public SchemaData(String tableName, String createTableStatement, @NonNull String... createIndexStatements) {
        this.tableName = tableName;
        this.createTableStatement = createTableStatement;
        this.createIndexStatements = new ArrayList<>(createIndexStatements.length);
        Collections.addAll(this.createIndexStatements, createIndexStatements);
    }

    @Override
    public String getTableName() {
        return tableName;
    }

    @Override
    public String getCreateTableStatement() {
        return createTableStatement;
    }

    @Override
    public List<String> getCreateIndexStatements() {
        return createIndexStatements;
    }

    public void addCreateIndexStatements(String... statements) {
        Collections.addAll(createIndexStatements, statements);
    }

    public List<String> getAllTheStatements() {
        List<String> statements = new ArrayList<>();
        statements.add(createTableStatement);
        statements.addAll(createIndexStatements);
        return statements;
    }
}


<|start_filename|>library/src/main/java/com/github/gfx/android/orma/BuiltInSerializers.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma;

import android.net.Uri;
import androidx.annotation.NonNull;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.JsonWriter;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Currency;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
 * Built-in serializes and deserializers.
 *
 * @see com.github.gfx.android.orma.annotation.StaticTypeAdapter
 */
public class BuiltInSerializers {

    @NonNull
    public static String serializeBigDecimal(@NonNull BigDecimal source) {
        return source.toString();
    }

    @NonNull
    public static BigDecimal deserializeBigDecimal(@NonNull String serialized) {
        return new BigDecimal(serialized);
    }

    @NonNull
    public static String serializeBigInteger(@NonNull BigInteger source) {
        return source.toString();
    }

    @NonNull
    public static BigInteger deserializeBigInteger(@NonNull String serialized) {
        return new BigInteger(serialized);
    }

    @NonNull
    public static byte[] serializeByteBuffer(@NonNull ByteBuffer source) {
        return source.array();
    }

    @NonNull
    public static ByteBuffer deserializeByteBuffer(@NonNull byte[] serialized) {
        return ByteBuffer.wrap(serialized);
    }

    @NonNull
    public static String serializeCurrency(@NonNull Currency source) {
        return source.getCurrencyCode();
    }

    @NonNull
    public static Currency deserializeCurrency(@NonNull String serialized) {
        return Currency.getInstance(serialized);
    }


    public static long serializeDate(@NonNull java.util.Date date) {
        return date.getTime();
    }

    @NonNull
    public static java.util.Date deserializeDate(long timeMillis) {
        return new java.util.Date(timeMillis);
    }

    @NonNull
    public static String serializeSqlDate(@NonNull java.sql.Date source) {
        return source.toString();
    }

    @NonNull
    public static java.sql.Date deserializeSqlDate(@NonNull String serialized) {
        return java.sql.Date.valueOf(serialized);
    }

    @NonNull
    public static String serializeSqlTime(@NonNull java.sql.Time source) {
        return source.toString();
    }

    @NonNull
    public static java.sql.Time deserializeSqlTime(@NonNull String serialized) {
        return java.sql.Time.valueOf(serialized);
    }

    @NonNull
    public static String serializeSqlTimestamp(@NonNull java.sql.Timestamp source) {
        return source.toString();
    }

    @NonNull
    public static java.sql.Timestamp deserializeSqlTimestamp(@NonNull String serialized) {
        return java.sql.Timestamp.valueOf(serialized);
    }

    @NonNull
    public static String serializeUri(@NonNull Uri source) {
        return source.toString();
    }

    @NonNull
    public static Uri deserializeUri(@NonNull String serialized) {
        return Uri.parse(serialized);
    }

    @NonNull
    public static String serializeUUID(@NonNull UUID source) {
        return source.toString();
    }

    @NonNull
    public static UUID deserializeUUID(@NonNull String serialized) {
        return UUID.fromString(serialized);
    }

    // collections

    /**
     * A generic serializer for string collections.
     *
     * @param collection Collection to serialize
     * @param <C>        A concrete collection class, e.g. {@code ArrayList<String>}.
     * @return JSON representation of the string collection.
     */
    @NonNull
    public static <C extends Collection<String>> String serializeStringCollection(@NonNull C collection) {
        StringWriter writer = new StringWriter();
        JsonWriter jsonWriter = new JsonWriter(writer);
        try {
            jsonWriter.beginArray();
            for (String s : collection) {
                jsonWriter.value(s);
            }
            jsonWriter.endArray();
            jsonWriter.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return writer.toString();
    }

    @SuppressWarnings("unchecked")
    private static <C extends Collection<String>> C createCollection(Class<?> collectionClass) {
        try {
            return (C)collectionClass.newInstance();
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    /**
     * A generic deserializer for string collections.
     *
     * @param serialized      JSON representation of a string collection.
     * @param collectionClass Concrete, instantiatable collection class, e.g. {@code ArrayList.class}.
     * @param <C>             A concrete collection class, e.g. {@code ArrayList<String>}.
     * @return A collection instance retrieved from {@code serialized}.
     */
    @NonNull
    public static <C extends Collection<String>> C deserializeStringCollection(@NonNull String serialized,
            @NonNull Class<?> collectionClass) {
        C collection = createCollection(collectionClass);

        StringReader reader = new StringReader(serialized);
        JsonReader jsonReader = new JsonReader(reader);

        try {
            jsonReader.beginArray();
            while (jsonReader.hasNext()) {
                if (jsonReader.peek() == JsonToken.NULL) {
                    jsonReader.nextNull();
                    collection.add(null);
                } else {
                    collection.add(jsonReader.nextString());
                }
            }
            jsonReader.endArray();
            jsonReader.close();
            return collection;
        } catch (IOException e) {
            return collection;
        }
    }

    @NonNull
    public static String serializeStringList(@NonNull List<String> source) {
        return serializeStringCollection(source);
    }

    @SuppressWarnings("unchecked")
    @NonNull
    public static List<String> deserializeStringList(@NonNull String serialized) {
        return deserializeStringCollection(serialized, ArrayList.class);
    }

    @NonNull
    public static String serializeStringSet(@NonNull Set<String> source) {
        return serializeStringCollection(source);
    }

    @SuppressWarnings("unchecked")
    @NonNull
    public static Set<String> deserializeStringSet(@NonNull String serialized) {
        return deserializeStringCollection(serialized, HashSet.class);
    }
}


<|start_filename|>migration/src/main/java/com/github/gfx/android/orma/migration/SQLiteMaster.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.migration;

import com.github.gfx.android.orma.core.Database;

import android.database.Cursor;
import androidx.annotation.NonNull;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeMap;

/**
 * Interface to the SQLite metadata table, {@code sqlite_master}.
 */
public class SQLiteMaster implements MigrationSchema {

    public static String TAG = "SQLiteMaster";

    public String type;

    public String name;

    public String tableName;

    public String sql;

    public List<SQLiteMaster> indexes = new ArrayList<>();

    public SQLiteMaster() {
    }

    public SQLiteMaster(String type, String name, String tableName, String sql) {
        this.type = type;
        this.name = name;
        this.tableName = tableName;
        this.sql = sql;
    }

    @NonNull
    public static boolean checkIfTableNameExists(@NonNull Database db, @NonNull String tableName) {
        return db.queryNumEntries("sqlite_master", "tbl_name = ?", new String[]{tableName}) != 0;
    }

    @NonNull
    public static SQLiteMaster findByTableName(@NonNull Database db, @NonNull String tableName) {
        Cursor cursor = db.rawQuery("SELECT type,name,tbl_name,sql FROM sqlite_master where tbl_name = ?",
                new String[]{tableName});
        try {
            Map<String, SQLiteMaster> tables = loadTables(cursor);
            if (tables.isEmpty()) {
                throw new NoSuchElementException("No such table: " + tableName);
            }
            return tables.get(tableName);
        } finally {
            cursor.close();
        }
    }

    @NonNull
    public static Map<String, SQLiteMaster> loadTables(@NonNull Database db) {
        Cursor cursor = db.rawQuery("SELECT type,name,tbl_name,sql FROM sqlite_master", null);
        try {
            return loadTables(cursor);
        } finally {
            cursor.close();
        }
    }

    @NonNull
    public static Map<String, SQLiteMaster> loadTables(Cursor cursor) {
        Map<String, SQLiteMaster> tables = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        if (cursor.moveToFirst()) {
            do {
                String type = cursor.getString(0); // "table" or "index"
                String name = cursor.getString(1); // table or index name
                String tableName = cursor.getString(2);
                String sql = cursor.getString(3);

                SQLiteMaster meta = tables.get(tableName);
                if (meta == null) {
                    meta = new SQLiteMaster();
                    tables.put(tableName, meta);
                }

                switch (type) {
                    case "table":
                        meta.type = type;
                        meta.name = name;
                        meta.tableName = tableName;
                        meta.sql = sql;
                        break;
                    case "index":
                        // sql=null for sqlite_autoindex_${table}_${columnIndex}
                        if (sql != null) {
                            meta.indexes.add(new SQLiteMaster(type, name, tableName, sql));
                        }
                        break;
                    default:
                        Log.w(TAG, "unsupported type:" + type);
                }
            } while (cursor.moveToNext());
        }
        return tables;
    }

    @Override
    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append(sql);
        s.append("; ");

        for (SQLiteMaster index : indexes) {
            s.append(index);
            s.append("; ");
        }
        s.setLength(s.length() - "; ".length());
        return s.toString();
    }

    @Override
    public String getTableName() {
        return tableName;
    }

    @Override
    public String getCreateTableStatement() {
        return sql;
    }

    @Override
    public List<String> getCreateIndexStatements() {
        List<String> createIndexStatements = new ArrayList<>();
        for (SQLiteMaster index : indexes) {
            createIndexStatements.add(index.sql);
        }
        return createIndexStatements;
    }
}


<|start_filename|>processor/src/main/java/com/github/gfx/android/orma/processor/generator/BaseWriter.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.processor.generator;

import com.github.gfx.android.orma.processor.ProcessingContext;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;

import java.util.Optional;

import javax.lang.model.element.Element;

public abstract class BaseWriter {

    protected final ProcessingContext context;

    public BaseWriter(ProcessingContext context) {
        this.context = context;
    }

    public abstract TypeSpec buildTypeSpec();

    public abstract String getPackageName();

    public abstract Optional<? extends Element> getElement();

    public JavaFile buildJavaFile() {
        return JavaFile.builder(getPackageName(), buildTypeSpec())
                .skipJavaLangImports(true)
                .build();
    }
}


<|start_filename|>library/src/test/java/com/github/gfx/android/orma/test/Issue463_ForOrmaDatabaseBuilder.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.BuildConfig;
import com.github.gfx.android.orma.test.model.OrmaDatabase;

import org.junit.Test;
import org.junit.runner.RunWith;

import android.content.Context;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

@RunWith(AndroidJUnit4.class)
public class Issue463_ForOrmaDatabaseBuilder {

    Context getContext() {
        return ApplicationProvider.getApplicationContext();
    }

    @Test
    public void dbVersionIsUpToDate() {
        OrmaDatabase orma = OrmaDatabase
                .builder(getContext())
                .name(null)
                .build();

        assertThat(orma.getConnection().getReadableDatabase().getVersion(), is(not(0)));
        assertThat(orma.getConnection().getReadableDatabase().getVersion(), is(BuildConfig.VERSION_CODE));
    }
}


<|start_filename|>example/src/main/java/com/github/gfx/android/orma/example/tool/LargeLog.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.example.tool;

import android.util.Log;

import static android.content.ContentValues.TAG;

public class LargeLog {

    // output all the large logs
    public static void e(String tag, String content) {
        if (content.length() > 2000) {
            Log.e(tag, content.substring(0, 2000));
            e(tag, content.substring(2000));
        } else {
            Log.e(tag, content);
        }
    }

    public static void e(String tag, Throwable throwable) {
        e(TAG, Log.getStackTraceString(throwable));
    }
}


<|start_filename|>example/build/generated/source/apt/normal/debug/com/github/gfx/android/orma/example/orma/Category_Updater.java<|end_filename|>
package com.github.gfx.android.orma.example.orma;

import androidx.annotation.NonNull;
import com.github.gfx.android.orma.rx.RxOrmaConnection;
import com.github.gfx.android.orma.rx.RxUpdater;
import java.util.Arrays;
import java.util.Collection;

public class Category_Updater extends RxUpdater<Category, Category_Updater> {
  final Category_Schema schema;

  public Category_Updater(RxOrmaConnection conn, Category_Schema schema) {
    super(conn);
    this.schema = schema;
  }

  public Category_Updater(Category_Updater that) {
    super(that);
    this.schema = that.getSchema();
  }

  public Category_Updater(Category_Relation relation) {
    super(relation);
    this.schema = relation.getSchema();
  }

  @Override
  public Category_Updater clone() {
    return new Category_Updater(this);
  }

  @NonNull
  @Override
  public Category_Schema getSchema() {
    return schema;
  }

  public Category_Updater name(@NonNull String name) {
    contents.put("`name`", name);
    return this;
  }

  public Category_Updater idEq(long id) {
    return where(schema.id, "=", id);
  }

  public Category_Updater idNotEq(long id) {
    return where(schema.id, "<>", id);
  }

  public Category_Updater idIn(@NonNull Collection<Long> values) {
    return in(false, schema.id, values);
  }

  public Category_Updater idNotIn(@NonNull Collection<Long> values) {
    return in(true, schema.id, values);
  }

  public final Category_Updater idIn(@NonNull Long... values) {
    return idIn(Arrays.asList(values));
  }

  public final Category_Updater idNotIn(@NonNull Long... values) {
    return idNotIn(Arrays.asList(values));
  }

  public Category_Updater idLt(long id) {
    return where(schema.id, "<", id);
  }

  public Category_Updater idLe(long id) {
    return where(schema.id, "<=", id);
  }

  public Category_Updater idGt(long id) {
    return where(schema.id, ">", id);
  }

  public Category_Updater idGe(long id) {
    return where(schema.id, ">=", id);
  }

  /**
   * To build a condition <code>id BETWEEN a AND b</code>, which is equivalent to <code>a <= id AND id <= b</code>.
   */
  public Category_Updater idBetween(long idA, long idB) {
    return whereBetween(schema.id, idA, idB);
  }
}


<|start_filename|>migration/src/test/java/com/github/gfx/android/orma/migration/test/OrmaMigrationTest.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.gfx.android.orma.migration.test;

import com.github.gfx.android.orma.core.Database;
import com.github.gfx.android.orma.core.DefaultDatabase;
import com.github.gfx.android.orma.migration.ManualStepMigration;
import com.github.gfx.android.orma.migration.OrmaMigration;
import com.github.gfx.android.orma.migration.SQLiteMaster;
import com.github.gfx.android.orma.migration.test.util.SchemaData;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import android.content.Context;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

@RunWith(AndroidJUnit4.class)
public class OrmaMigrationTest {

    static String SCHEMA_HASH = "aaa";

    static int VERSION = 100;

    Database db;

    OrmaMigration migration;

    static Context getContext() {
        return ApplicationProvider.getApplicationContext();
    }

    @Before
    public void setUp() throws Exception {
        db = new DefaultDatabase.Provider().provideOnMemoryDatabase(getContext());
        db.setVersion(1);

        migration = OrmaMigration.builder(getContext())
                .schemaHashForSchemaDiffMigration(SCHEMA_HASH)
                .versionForManualStepMigration(VERSION)
                .trace(true)
                .build();

        migration.getManualStepMigration()
                .execStep(db, 1, "CREATE TABLE dummy (id INTEGER PRIMARY KEY)");
    }

    @Test
    public void testBuilder() throws Exception {
        migration = OrmaMigration.builder(getContext())
                .schemaHashForSchemaDiffMigration(SCHEMA_HASH)
                .versionForManualStepMigration(VERSION)
                .step(2, new ManualStepMigration.ChangeStep() {
                    @Override
                    public void change(@NonNull ManualStepMigration.Helper helper) {
                        helper.execSQL("CREATE TABLE step_2 (id INTEGER PRIMARY KEY)");
                    }
                })
                .step(4, new ManualStepMigration.ChangeStep() {
                    @Override
                    public void change(@NonNull ManualStepMigration.Helper helper) {
                        helper.execSQL("CREATE TABLE step_4 (id INTEGER PRIMARY KEY)");
                    }
                })
                .step(8, new ManualStepMigration.ChangeStep() {
                    @Override
                    public void change(@NonNull ManualStepMigration.Helper helper) {
                        helper.execSQL("CREATE TABLE step_8 (id INTEGER PRIMARY KEY)");
                    }
                })
                .step(16, new ManualStepMigration.ChangeStep() {
                    @Override
                    public void change(@NonNull ManualStepMigration.Helper helper) {
                        helper.execSQL("CREATE TABLE step_16 (id INTEGER PRIMARY KEY)");
                    }
                })
                .build();

        migration.start(db, new ArrayList<SchemaData>());
        migration.start(db, new ArrayList<SchemaData>());
        migration.start(db, new ArrayList<SchemaData>());

        Map<String, SQLiteMaster> tables = SQLiteMaster.loadTables(db);
        assertThat(tables.containsKey("step_2"), is(true));
        assertThat(tables.containsKey("step_4"), is(true));
        assertThat(tables.containsKey("step_8"), is(true));
        assertThat(tables.containsKey("step_16"), is(true));
    }

    @Test
    public void testHomeostasis() throws Exception {
        migration.addStep(2, new ManualStepMigration.ChangeStep() {
            @Override
            public void change(@NonNull ManualStepMigration.Helper helper) {
                helper.execSQL("CREATE TABLE step_2 (id INTEGER PRIMARY KEY)");
            }
        });
        migration.addStep(4, new ManualStepMigration.ChangeStep() {
            @Override
            public void change(@NonNull ManualStepMigration.Helper helper) {
                helper.execSQL("CREATE TABLE step_4 (id INTEGER PRIMARY KEY)");
            }
        });
        migration.addStep(8, new ManualStepMigration.ChangeStep() {
            @Override
            public void change(@NonNull ManualStepMigration.Helper helper) {
                helper.execSQL("CREATE TABLE step_8 (id INTEGER PRIMARY KEY)");
            }
        });

        migration.addStep(16, new ManualStepMigration.ChangeStep() {
            @Override
            public void change(@NonNull ManualStepMigration.Helper helper) {
                helper.execSQL("CREATE TABLE step_16 (id INTEGER PRIMARY KEY)");
            }
        });

        migration.start(db, new ArrayList<SchemaData>());
        migration.start(db, new ArrayList<SchemaData>());
        migration.start(db, new ArrayList<SchemaData>());

        Map<String, SQLiteMaster> tables = SQLiteMaster.loadTables(db);
        assertThat(tables.containsKey("step_2"), is(true));
        assertThat(tables.containsKey("step_4"), is(true));
        assertThat(tables.containsKey("step_8"), is(true));
        assertThat(tables.containsKey("step_16"), is(true));
    }

    @Test
    public void testForceRecreate() throws Exception {
        // initialize
        String[] initData = {
                "CREATE TABLE foo (id PRIMARY KEY)",
                "CREATE TABLE bar (id PRIMARY KEY)",

                "INSERT INTO foo (id) VALUES (1)",
                "INSERT INTO bar (id) VALUES (2)",
        };

        for (String s : initData) {
            migration.getManualStepMigration().execStep(db, 2, s);
        }

        // define steps
        migration.addStep(3, new ManualStepMigration.ChangeStep() {
            @Override
            public void change(@NonNull ManualStepMigration.Helper helper) {
                helper.execSQL("DROP TABLE foo");
                helper.execSQL("DROP TABLE bar");
            }
        });

        List<SchemaData> schemas = Arrays.asList(
                new SchemaData("foo", "CREATE TABLE foo (id PRIMARY KEY, field01 TEXT NOT NULL, field02 TEXT NOT NULL)"),
                new SchemaData("bar", "CREATE TABLE bar (id PRIMARY KEY, field10 TEXT NOT NULL, field20 TEXT NOT NULL)")
        );

        // start migration
        migration.start(db, schemas);

        Map<String, SQLiteMaster> tables = SQLiteMaster.loadTables(db);
        assertThat(tables, hasKey("foo"));
        assertThat(tables, hasKey("bar"));
        assertThat(tables.get("foo").sql, is(schemas.get(0).getCreateTableStatement()));
        assertThat(tables.get("bar").sql, is(schemas.get(1).getCreateTableStatement()));
    }
}


<|start_filename|>example/build/generated/source/apt/normal/debug/com/github/gfx/android/orma/example/orma/Todo_AssociationCondition.java<|end_filename|>
package com.github.gfx.android.orma.example.orma;

import androidx.annotation.NonNull;
import com.github.gfx.android.orma.AssociationCondition;
import com.github.gfx.android.orma.OrmaConnection;
import java.util.Arrays;
import java.util.Collection;

public class Todo_AssociationCondition extends AssociationCondition<Todo, Todo_AssociationCondition> {
  final Todo_Schema schema;

  public Todo_AssociationCondition(OrmaConnection conn, Todo_Schema schema) {
    super(conn);
    this.schema = schema;
  }

  public Todo_AssociationCondition(Todo_AssociationCondition that) {
    super(that);
    this.schema = that.getSchema();
  }

  public Todo_AssociationCondition(Todo_Relation relation) {
    super(relation);
    this.schema = relation.getSchema();
  }

  @Override
  public Todo_AssociationCondition clone() {
    return new Todo_AssociationCondition(this);
  }

  @NonNull
  @Override
  public Todo_Schema getSchema() {
    return schema;
  }

  public Todo_AssociationCondition titleEq(@NonNull String title) {
    return where(schema.title, "=", title);
  }

  public Todo_AssociationCondition titleNotEq(@NonNull String title) {
    return where(schema.title, "<>", title);
  }

  public Todo_AssociationCondition titleIn(@NonNull Collection<String> values) {
    return in(false, schema.title, values);
  }

  public Todo_AssociationCondition titleNotIn(@NonNull Collection<String> values) {
    return in(true, schema.title, values);
  }

  public final Todo_AssociationCondition titleIn(@NonNull String... values) {
    return titleIn(Arrays.asList(values));
  }

  public final Todo_AssociationCondition titleNotIn(@NonNull String... values) {
    return titleNotIn(Arrays.asList(values));
  }

  public Todo_AssociationCondition titleGlob(@NonNull String pattern) {
    return where(schema.title, "GLOB", pattern);
  }

  public Todo_AssociationCondition titleNotGlob(@NonNull String pattern) {
    return where(schema.title, "NOT GLOB", pattern);
  }

  public Todo_AssociationCondition titleLike(@NonNull String pattern) {
    return where(schema.title, "LIKE", pattern);
  }

  public Todo_AssociationCondition titleNotLike(@NonNull String pattern) {
    return where(schema.title, "NOT LIKE", pattern);
  }

  public Todo_AssociationCondition titleLt(@NonNull String title) {
    return where(schema.title, "<", title);
  }

  public Todo_AssociationCondition titleLe(@NonNull String title) {
    return where(schema.title, "<=", title);
  }

  public Todo_AssociationCondition titleGt(@NonNull String title) {
    return where(schema.title, ">", title);
  }

  public Todo_AssociationCondition titleGe(@NonNull String title) {
    return where(schema.title, ">=", title);
  }

  public Todo_AssociationCondition doneEq(boolean done) {
    return where(schema.done, "=", done);
  }

  public Todo_AssociationCondition doneNotEq(boolean done) {
    return where(schema.done, "<>", done);
  }

  public Todo_AssociationCondition doneIn(@NonNull Collection<Boolean> values) {
    return in(false, schema.done, values);
  }

  public Todo_AssociationCondition doneNotIn(@NonNull Collection<Boolean> values) {
    return in(true, schema.done, values);
  }

  public final Todo_AssociationCondition doneIn(@NonNull Boolean... values) {
    return doneIn(Arrays.asList(values));
  }

  public final Todo_AssociationCondition doneNotIn(@NonNull Boolean... values) {
    return doneNotIn(Arrays.asList(values));
  }

  public Todo_AssociationCondition doneLt(boolean done) {
    return where(schema.done, "<", done);
  }

  public Todo_AssociationCondition doneLe(boolean done) {
    return where(schema.done, "<=", done);
  }

  public Todo_AssociationCondition doneGt(boolean done) {
    return where(schema.done, ">", done);
  }

  public Todo_AssociationCondition doneGe(boolean done) {
    return where(schema.done, ">=", done);
  }

  public Todo_AssociationCondition idEq(long id) {
    return where(schema.id, "=", id);
  }

  public Todo_AssociationCondition idNotEq(long id) {
    return where(schema.id, "<>", id);
  }

  public Todo_AssociationCondition idIn(@NonNull Collection<Long> values) {
    return in(false, schema.id, values);
  }

  public Todo_AssociationCondition idNotIn(@NonNull Collection<Long> values) {
    return in(true, schema.id, values);
  }

  public final Todo_AssociationCondition idIn(@NonNull Long... values) {
    return idIn(Arrays.asList(values));
  }

  public final Todo_AssociationCondition idNotIn(@NonNull Long... values) {
    return idNotIn(Arrays.asList(values));
  }

  public Todo_AssociationCondition idLt(long id) {
    return where(schema.id, "<", id);
  }

  public Todo_AssociationCondition idLe(long id) {
    return where(schema.id, "<=", id);
  }

  public Todo_AssociationCondition idGt(long id) {
    return where(schema.id, ">", id);
  }

  public Todo_AssociationCondition idGe(long id) {
    return where(schema.id, ">=", id);
  }

  /**
   * To build a condition <code>id BETWEEN a AND b</code>, which is equivalent to <code>a <= id AND id <= b</code>.
   */
  public Todo_AssociationCondition idBetween(long idA, long idB) {
    return whereBetween(schema.id, idA, idB);
  }
}


<|start_filename|>processor/src/main/java/com/github/gfx/android/orma/processor/model/DatabaseDefinition.java<|end_filename|>
/*
 * Copyright (c) 2015 <NAME> (gfx).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.gfx.android.orma.processor.model;

import com.github.gfx.android.orma.annotation.Database;
import com.github.gfx.android.orma.processor.ProcessingContext;
import com.github.gfx.android.orma.processor.tool.AnnotationHandle;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

public class DatabaseDefinition {

    final ProcessingContext context;

    final TypeElement element;

    final String packageName;

    final ClassName className;

    final boolean rxJavaSupport;

    final Set<TypeName> includes;

    final Set<TypeName> excludes;

    public DatabaseDefinition(ProcessingContext context, TypeElement element) {
        this.context = context;
        this.element = element;
        AnnotationHandle<Database> database = AnnotationHandle.find(element, Database.class).get();

        packageName = context.getElements().getPackageOf(element).toString();
        String name = database.getOrDefault("databaseClassName", String.class);
        className = ClassName.get(packageName, name);

        rxJavaSupport = database.getOrDefault("rxJavaSupport", Boolean.class);

        includes = database.getValues("includes", TypeMirror.class)
                .map(ClassName::get)
                .collect(Collectors.toSet());

        excludes = database.getValues("excludes", TypeMirror.class)
                .map(ClassName::get)
                .collect(Collectors.toSet());
    }

    public DatabaseDefinition(ProcessingContext context, String packageName, String className, boolean rxJavaSupport) {
        this.context = context;
        this.element = null;
        this.packageName = packageName;
        this.className = ClassName.get(packageName, className);
        this.rxJavaSupport = rxJavaSupport;
        this.includes = Collections.emptySet();
        this.excludes = Collections.emptySet();
    }

    public Optional<TypeElement> getElement() {
        return Optional.ofNullable(element);
    }

    public List<SchemaDefinition> getSchemas() {
        Stream<SchemaDefinition> stream = context.schemaMap.values().stream();

        if (!includes.isEmpty()) {
            stream = stream.filter(schema -> includes.contains(schema.getModelClassName()));
        }
        if (!excludes.isEmpty()) {
            stream = stream.filter(schema -> !excludes.contains(schema.getModelClassName()));
        }
        return stream.collect(Collectors.toList());
    }

    public String getPackageName() {
        return packageName;
    }

    public ClassName getClassName() {
        return className;
    }

    public boolean isRxJavaSupport() {
        return rxJavaSupport;
    }
}
