- * Example: one "Order" references one "Customer" (to-one relation).
+ * Example (to-one relation): one "Order" references one "Customer".
* The backlink to this is a to-many in the reverse direction: one "Customer" has a number of "Order"s.
- *
- * Note: backlinks to to-many relations will be supported in the future.
+ *
+ * Example (to-many relation): one "Teacher" references multiple "Student"s.
+ * The backlink to this: one "Student" has a number of "Teacher"s.
+ *
+ * Note: changes made to a backlink relation based on a to-many relation are ignored.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
@Beta
public @interface Backlink {
/**
- * Name of the relation the backlink should be based on (e.g. name of a ToOne property in the target entity).
+ * Name of the relation the backlink should be based on (e.g. name of a ToOne or ToMany property in the target entity).
* Can be left empty if there is just a single relation from the target to the source entity.
*/
String to() default "";
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/DefaultValue.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/DefaultValue.java
new file mode 100644
index 00000000..1b50f0e5
--- /dev/null
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/DefaultValue.java
@@ -0,0 +1,18 @@
+package io.objectbox.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Defines the Java code of the default value to use for a property, when getting an existing entity and the database
+ * value for the property is null.
+ *
+ * Currently only {@code @DefaultValue("")} is supported.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD})
+public @interface DefaultValue {
+ String value();
+}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/Id.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/Id.java
index d5c01b34..c07579f8 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/Id.java
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/Id.java
@@ -22,7 +22,11 @@
import java.lang.annotation.Target;
/**
- * Marks field is the primary key of the entity's table
+ * Marks the ID property of an {@link Entity @Entity}.
+ * The property must be of type long (or Long in Kotlin) and have not-private visibility
+ * (or a not-private getter and setter method).
+ *
+ * ID properties are unique and indexed by default.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
@@ -35,8 +39,10 @@
// boolean monotonic() default false;
/**
- * Allows IDs to be assigned by the developer. This may make sense for using IDs originating somewhere else, e.g.
- * from the server.
+ * Allows IDs of new entities to be assigned manually.
+ * Warning: This has side effects, check the online documentation on self-assigned object IDs for details.
+ *
+ * This may allow re-use of IDs assigned elsewhere, e.g. by a server.
*/
boolean assignable() default false;
}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/Index.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/Index.java
index 863a0ff4..123d239a 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/Index.java
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/Index.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2017-2018 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,13 +22,19 @@
import java.lang.annotation.Target;
/**
- * Specifies that the property should be indexed, which is highly recommended if you do queries using this property.
+ * Specifies that the property should be indexed.
+ *
+ * It is highly recommended to index properties that are used in a query to improve query performance.
+ *
+ * To fine tune indexing of a property you can override the default index {@link #type()}.
+ *
+ * Note: indexes are currently not supported for byte array, float or double properties.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Index {
-// /**
-// * Whether the unique constraint should be created with base on this index
-// */
-// boolean unique() default false;
+ /**
+ * Sets the {@link IndexType}, defaults to {@link IndexType#DEFAULT}.
+ */
+ IndexType type() default IndexType.DEFAULT;
}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/IndexType.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/IndexType.java
new file mode 100644
index 00000000..33217349
--- /dev/null
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/IndexType.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 ObjectBox Ltd. All rights reserved.
+ *
+ * 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 io.objectbox.annotation;
+
+/**
+ * ObjectBox offers a value and two hash index types, from which it chooses a reasonable default (see {@link #DEFAULT}).
+ *
+ * For some queries/use cases it might make sense to override the default choice for optimization purposes.
+ *
+ * Note: hash indexes are currently only supported for string properties.
+ */
+public enum IndexType {
+ /**
+ * Use the default index type depending on the property type:
+ * {@link #VALUE} for scalars and {@link #HASH} for Strings.
+ */
+ DEFAULT,
+
+ /**
+ * Use the property value to build the index.
+ * For Strings this may occupy more space than the default setting.
+ */
+ VALUE,
+
+ /**
+ * Use a (fast non-cryptographic) hash of the property value to build the index.
+ * Internally, it uses a 32 bit hash with a decent hash collision behavior.
+ * Because occasional collisions do not really impact performance, this is usually a better choice than
+ * {@link #HASH64} as it takes less space.
+ */
+ HASH,
+
+ /**
+ * Use a long (fast non-cryptographic) hash of the property value to build the index.
+ */
+ HASH64
+}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/Keep.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/Keep.java
deleted file mode 100644
index 2ed50dd5..00000000
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/Keep.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
- *
- * 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 io.objectbox.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Specifies that the target should be kept during next run of ObjectBox generation.
- *
- * Using this annotation on an Entity class itself silently disables any class modification.
- * The user is responsible to write and support any code which is required for ObjectBox.
- *
- *
- * Don't use this annotation on a class member if you are not completely sure what you are doing, because in
- * case of model changes ObjectBox will not be able to make corresponding changes into the code of the target.
- *
- *
- * @see Generated
- */
-@Retention(RetentionPolicy.CLASS)
-@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})
-@Deprecated
-public @interface Keep {
-}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/NotNull.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/NotNull.java
index b2ab6bc3..4e6f2681 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/NotNull.java
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/NotNull.java
@@ -30,5 +30,5 @@
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
-/** TODO public */ @interface NotNull {
+/* TODO public */ @interface NotNull {
}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/OrderBy.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/OrderBy.java
index 8264f849..5ec7d2f0 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/OrderBy.java
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/OrderBy.java
@@ -28,7 +28,7 @@
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
-/** TODO public */ @interface OrderBy {
+/* TODO public */ @interface OrderBy {
/**
* Comma-separated list of properties, e.g. "propertyA, propertyB, propertyC"
* To specify direction, add ASC or DESC after property name, e.g.: "propertyA DESC, propertyB ASC"
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/Relation.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/Relation.java
deleted file mode 100644
index d6f54173..00000000
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/Relation.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
- *
- * 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 io.objectbox.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import io.objectbox.annotation.apihint.Beta;
-import io.objectbox.annotation.apihint.Temporary;
-
-/**
- * Optional annotation for ToOnes to specify a property serving as an ID to the target.
- * Note: this annotation will likely be renamed/changed in the next version.
- */
-@Retention(RetentionPolicy.CLASS)
-@Target(ElementType.FIELD)
-@Beta
-@Temporary
-@Deprecated
-public @interface Relation {
- /**
- * Name of the property (in the source entity) holding the id (key) as a base for this to-one relation.
- */
- String idProperty() default "";
-}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/Generated.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/Unique.java
similarity index 65%
rename from objectbox-java-api/src/main/java/io/objectbox/annotation/Generated.java
rename to objectbox-java-api/src/main/java/io/objectbox/annotation/Unique.java
index c7d7465b..a7963feb 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/Generated.java
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/Unique.java
@@ -22,16 +22,14 @@
import java.lang.annotation.Target;
/**
- * Marks that a field, constructor or method was generated by ObjectBox
- * All the code elements that are marked with this annotation can be changed/removed during next run of generation in
- * respect of model changes.
- *
- * @see Keep
+ * Enforces that the value of a property is unique among all objects in a box before an object can be put.
+ *
+ * Trying to put an object with offending values will result in a UniqueViolationException.
+ *
+ * Unique properties are based on an {@link Index @Index}, so the same restrictions apply.
+ * It is supported to explicitly add the {@link Index @Index} annotation to configure the index.
*/
@Retention(RetentionPolicy.CLASS)
-@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
-@Deprecated
-public @interface Generated {
- /** A hash to identify the generated code */
- int value() default -1;
+@Target(ElementType.FIELD)
+public @interface Unique {
}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/apihint/package-info.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/apihint/package-info.java
new file mode 100644
index 00000000..6bcafc47
--- /dev/null
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/apihint/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2019 ObjectBox Ltd. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * Annotations to mark APIs as for example {@link io.objectbox.annotation.apihint.Internal @Internal}
+ * or {@link io.objectbox.annotation.apihint.Experimental @Experimental}.
+ */
+package io.objectbox.annotation.apihint;
\ No newline at end of file
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/package-info.java b/objectbox-java-api/src/main/java/io/objectbox/annotation/package-info.java
new file mode 100644
index 00000000..65c31fb1
--- /dev/null
+++ b/objectbox-java-api/src/main/java/io/objectbox/annotation/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 ObjectBox Ltd. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * Annotations to mark a class as an {@link io.objectbox.annotation.Entity @Entity},
+ * to specify the {@link io.objectbox.annotation.Id @Id} property,
+ * to create an {@link io.objectbox.annotation.Index @Index} or
+ * a {@link io.objectbox.annotation.Transient @Transient} property.
+ *
+ * For more details look at the documentation of individual classes and
+ * docs.objectbox.io/entity-annotations.
+ */
+package io.objectbox.annotation;
\ No newline at end of file
diff --git a/objectbox-java-api/src/main/java/io/objectbox/converter/PropertyConverter.java b/objectbox-java-api/src/main/java/io/objectbox/converter/PropertyConverter.java
index 7c3369ff..6d65717f 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/converter/PropertyConverter.java
+++ b/objectbox-java-api/src/main/java/io/objectbox/converter/PropertyConverter.java
@@ -23,6 +23,8 @@
*
*
Converters are created by the default constructor
*
Converters must be implemented thread-safe
+ *
Converters must not interact with the database (such as using Box or BoxStore)
+ *
Converters must handle null values
*
*/
public interface PropertyConverter
{
diff --git a/objectbox-java-api/src/main/java/io/objectbox/converter/package-info.java b/objectbox-java-api/src/main/java/io/objectbox/converter/package-info.java
new file mode 100644
index 00000000..2c11b294
--- /dev/null
+++ b/objectbox-java-api/src/main/java/io/objectbox/converter/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 ObjectBox Ltd. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * For use with {@link io.objectbox.annotation.Convert @Convert}: {@link io.objectbox.converter.PropertyConverter}
+ * to convert custom property types.
+ *
+ * No {@link BoxStore} may be alive using the given directory.
+ *
* If you did not use a custom name with BoxStoreBuilder, you can pass "new File({@link
* BoxStoreBuilder#DEFAULT_NAME})".
*
@@ -452,11 +522,15 @@ public boolean deleteAllFiles() {
* BoxStoreBuilder#directory(File)}
* @return true if the directory 1) was deleted successfully OR 2) did not exist in the first place.
* Note: If false is returned, any number of files may have been deleted before the failure happened.
+ * @throws IllegalStateException if the given directory is still used by a open {@link BoxStore}.
*/
public static boolean deleteAllFiles(File objectStoreDirectory) {
if (!objectStoreDirectory.exists()) {
return true;
}
+ if (isFileOpen(getCanonicalPath(objectStoreDirectory))) {
+ throw new IllegalStateException("Cannot delete files: store is still open");
+ }
File[] files = objectStoreDirectory.listFiles();
if (files == null) {
@@ -476,6 +550,8 @@ public static boolean deleteAllFiles(File objectStoreDirectory) {
/**
* Danger zone! This will delete all files in the given directory!
*
+ * No {@link BoxStore} may be alive using the given name.
+ *
* If you did not use a custom name with BoxStoreBuilder, you can pass "new File({@link
* BoxStoreBuilder#DEFAULT_NAME})".
*
@@ -484,6 +560,7 @@ public static boolean deleteAllFiles(File objectStoreDirectory) {
* BoxStoreBuilder#name(String)}.
* @return true if the directory 1) was deleted successfully OR 2) did not exist in the first place.
* Note: If false is returned, any number of files may have been deleted before the failure happened.
+ * @throws IllegalStateException if the given name is still used by a open {@link BoxStore}.
*/
public static boolean deleteAllFiles(Object androidContext, @Nullable String customDbNameOrNull) {
File dbDir = BoxStoreBuilder.getAndroidDbDir(androidContext, customDbNameOrNull);
@@ -493,6 +570,8 @@ public static boolean deleteAllFiles(Object androidContext, @Nullable String cus
/**
* Danger zone! This will delete all files in the given directory!
*
+ * No {@link BoxStore} may be alive using the given directory.
+ *
* If you did not use a custom name with BoxStoreBuilder, you can pass "new File({@link
* BoxStoreBuilder#DEFAULT_NAME})".
*
@@ -502,12 +581,32 @@ public static boolean deleteAllFiles(Object androidContext, @Nullable String cus
* BoxStoreBuilder#name(String)}.
* @return true if the directory 1) was deleted successfully OR 2) did not exist in the first place.
* Note: If false is returned, any number of files may have been deleted before the failure happened.
+ * @throws IllegalStateException if the given directory (+name) is still used by a open {@link BoxStore}.
*/
public static boolean deleteAllFiles(@Nullable File baseDirectoryOrNull, @Nullable String customDbNameOrNull) {
File dbDir = BoxStoreBuilder.getDbDir(baseDirectoryOrNull, customDbNameOrNull);
return deleteAllFiles(dbDir);
}
+ /**
+ * Removes all objects from all types ("boxes"), e.g. deletes all database content
+ * (excluding meta data like the data model).
+ * This typically performs very quickly (e.g. faster than {@link Box#removeAll()}).
+ *
+ * Note that this does not reclaim disk space: the already reserved space for the DB file(s) is used in the future
+ * resulting in better performance because no/less disk allocation has to be done.
+ *
+ * If you want to reclaim disk space, delete the DB file(s) instead:
+ *
+ *
{@link #close()} the BoxStore (and ensure that no thread access it)
+ *
{@link #deleteAllFiles()} of the BoxStore
+ *
Open a new BoxStore
+ *
+ */
+ public void removeAllObjects() {
+ nativeDropAllData(handle);
+ }
+
@Internal
public void unregisterTransaction(Transaction transaction) {
synchronized (transactions) {
@@ -515,11 +614,6 @@ public void unregisterTransaction(Transaction transaction) {
}
}
- // TODO not implemented on native side; rename to "nukeData" (?)
- void dropAllData() {
- nativeDropAllData(handle);
- }
-
void txCommitted(Transaction tx, @Nullable int[] entityTypeIdsAffected) {
// Only one write TX at a time, but there is a chance two writers race after commit: thus synchronize
synchronized (txCommitCountLock) {
@@ -530,7 +624,7 @@ void txCommitted(Transaction tx, @Nullable int[] entityTypeIdsAffected) {
}
}
- for (Box box : boxes.values()) {
+ for (Box> box : boxes.values()) {
box.txCommitted(tx);
}
@@ -541,10 +635,12 @@ void txCommitted(Transaction tx, @Nullable int[] entityTypeIdsAffected) {
/**
* Returns a Box for the given type. Objects are put into (and get from) their individual Box.
+ *
+ * Creates a Box only once and then always returns the cached instance.
*/
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("unchecked") // Casting is easier than writing a custom Map.
public Box boxFor(Class entityClass) {
- Box box = boxes.get(entityClass);
+ Box box = (Box) boxes.get(entityClass);
if (box == null) {
if (!dbNameByClass.containsKey(entityClass)) {
throw new IllegalArgumentException(entityClass +
@@ -552,7 +648,7 @@ public Box boxFor(Class entityClass) {
}
// Ensure a box is created just once
synchronized (boxes) {
- box = boxes.get(entityClass);
+ box = (Box) boxes.get(entityClass);
if (box == null) {
box = new Box<>(this, entityClass);
boxes.put(entityClass, box);
@@ -569,7 +665,7 @@ public Box boxFor(Class entityClass) {
* disk synchronization.
*/
public void runInTx(Runnable runnable) {
- Transaction tx = this.activeTx.get();
+ Transaction tx = activeTx.get();
// Only if not already set, allowing to call it recursively with first (outer) TX
if (tx == null) {
tx = beginTx();
@@ -596,7 +692,7 @@ public void runInTx(Runnable runnable) {
* it is advised to run them in a single read transaction for efficiency reasons.
*/
public void runInReadTx(Runnable runnable) {
- Transaction tx = this.activeTx.get();
+ Transaction tx = activeTx.get();
// Only if not already set, allowing to call it recursively with first (outer) TX
if (tx == null) {
tx = beginReadTx();
@@ -608,7 +704,7 @@ public void runInReadTx(Runnable runnable) {
// TODO That's rather a quick fix, replace with a more general solution
// (that could maybe be a TX listener with abort callback?)
- for (Box box : boxes.values()) {
+ for (Box> box : boxes.values()) {
box.readTxFinished(tx);
}
@@ -652,7 +748,6 @@ public T callInReadTxWithRetry(Callable callable, int attempts, int initi
cleanStaleReadTransactions();
}
if (failedReadTxAttemptCallback != null) {
- //noinspection unchecked
failedReadTxAttemptCallback.txFinished(null, new DbException(message + " \n" + diagnose, e));
}
try {
@@ -676,7 +771,7 @@ public T callInReadTxWithRetry(Callable callable, int attempts, int initi
* not a RuntimeException itself.
*/
public T callInReadTx(Callable callable) {
- Transaction tx = this.activeTx.get();
+ Transaction tx = activeTx.get();
// Only if not already set, allowing to call it recursively with first (outer) TX
if (tx == null) {
tx = beginReadTx();
@@ -692,7 +787,7 @@ public T callInReadTx(Callable callable) {
// TODO That's rather a quick fix, replace with a more general solution
// (that could maybe be a TX listener with abort callback?)
- for (Box box : boxes.values()) {
+ for (Box> box : boxes.values()) {
box.readTxFinished(tx);
}
@@ -711,7 +806,7 @@ public T callInReadTx(Callable callable) {
* Like {@link #runInTx(Runnable)}, but allows returning a value and throwing an exception.
*/
public R callInTx(Callable callable) throws Exception {
- Transaction tx = this.activeTx.get();
+ Transaction tx = activeTx.get();
// Only if not already set, allowing to call it recursively with first (outer) TX
if (tx == null) {
tx = beginTx();
@@ -751,18 +846,15 @@ public R callInTxNoException(Callable callable) {
* See also {@link #runInTx(Runnable)}.
*/
public void runInTxAsync(final Runnable runnable, @Nullable final TxCallback callback) {
- threadPool.submit(new Runnable() {
- @Override
- public void run() {
- try {
- runInTx(runnable);
- if (callback != null) {
- callback.txFinished(null, null);
- }
- } catch (Throwable failure) {
- if (callback != null) {
- callback.txFinished(null, failure);
- }
+ threadPool.submit(() -> {
+ try {
+ runInTx(runnable);
+ if (callback != null) {
+ callback.txFinished(null, null);
+ }
+ } catch (Throwable failure) {
+ if (callback != null) {
+ callback.txFinished(null, failure);
}
}
});
@@ -775,18 +867,15 @@ public void run() {
* * See also {@link #callInTx(Callable)}.
*/
public void callInTxAsync(final Callable callable, @Nullable final TxCallback callback) {
- threadPool.submit(new Runnable() {
- @Override
- public void run() {
- try {
- R result = callInTx(callable);
- if (callback != null) {
- callback.txFinished(result, null);
- }
- } catch (Throwable failure) {
- if (callback != null) {
- callback.txFinished(null, failure);
- }
+ threadPool.submit(() -> {
+ try {
+ R result = callInTx(callable);
+ if (callback != null) {
+ callback.txFinished(result, null);
+ }
+ } catch (Throwable failure) {
+ if (callback != null) {
+ callback.txFinished(null, failure);
}
}
});
@@ -811,7 +900,7 @@ public int cleanStaleReadTransactions() {
* {@link Box#closeThreadResources()} for all initiated boxes ({@link #boxFor(Class)}).
*/
public void closeThreadResources() {
- for (Box box : boxes.values()) {
+ for (Box> box : boxes.values()) {
box.closeThreadResources();
}
// activeTx is cleaned up in finally blocks, so do not free them here
@@ -898,7 +987,7 @@ public SubscriptionBuilder> subscribe(Class forClass) {
}
@Internal
- public Future internalScheduleThread(Runnable runnable) {
+ public Future> internalScheduleThread(Runnable runnable) {
return threadPool.submit(runnable);
}
@@ -918,7 +1007,7 @@ public int internalQueryAttempts() {
}
@Internal
- public TxCallback internalFailedReadTxAttemptCallback() {
+ public TxCallback> internalFailedReadTxAttemptCallback() {
return failedReadTxAttemptCallback;
}
@@ -930,4 +1019,23 @@ long panicModeRemoveAllObjects(int entityId) {
return nativePanicModeRemoveAllObjects(handle, entityId);
}
+ /**
+ * If you want to use the same ObjectBox store using the C API, e.g. via JNI, this gives the required pointer,
+ * which you have to pass on to obx_store_wrap().
+ * The procedure is like this:
+ * 1) you create a BoxStore on the Java side
+ * 2) you call this method to get the native store pointer
+ * 3) you pass the native store pointer to your native code (e.g. via JNI)
+ * 4) your native code calls obx_store_wrap() with the native store pointer to get a OBX_store pointer
+ * 5) Using the OBX_store pointer, you can use the C API.
+ *
+ * Note: Once you {@link #close()} this BoxStore, do not use it from the C API.
+ */
+ public long getNativeStore() {
+ if (closed) {
+ throw new IllegalStateException("Store must still be open");
+ }
+ return handle;
+ }
+
}
diff --git a/objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java b/objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java
index 271aea89..583a176c 100644
--- a/objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java
+++ b/objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java
@@ -22,12 +22,12 @@
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
@@ -66,6 +66,10 @@ public class BoxStoreBuilder {
/** BoxStore uses this */
File directory;
+ /** On Android used for native library loading. */
+ @Nullable Object context;
+ @Nullable Object relinker;
+
/** Ignored by BoxStore */
private File baseDirectory;
@@ -87,9 +91,9 @@ public class BoxStoreBuilder {
int queryAttempts;
- TxCallback failedReadTxAttemptCallback;
+ TxCallback> failedReadTxAttemptCallback;
- final List entityInfoList = new ArrayList<>();
+ final List> entityInfoList = new ArrayList<>();
private Factory initialDbFileFactory;
/** Not for application use. */
@@ -101,13 +105,14 @@ private BoxStoreBuilder() {
model = null;
}
- @Internal
/** Called internally from the generated class "MyObjectBox". Check MyObjectBox.builder() to get an instance. */
+ @Internal
public BoxStoreBuilder(byte[] model) {
- this.model = model;
if (model == null) {
throw new IllegalArgumentException("Model may not be null");
}
+ // Future-proofing: copy to prevent external modification.
+ this.model = Arrays.copyOf(model, model.length);
}
/**
@@ -171,9 +176,12 @@ public BoxStoreBuilder baseDirectory(File baseDirectory) {
* Alternatively, you can also use {@link #baseDirectory} or {@link #directory(File)} instead.
*/
public BoxStoreBuilder androidContext(Object context) {
+ //noinspection ConstantConditions Annotation does not enforce non-null.
if (context == null) {
throw new NullPointerException("Context may not be null");
}
+ this.context = getApplicationContext(context);
+
File baseDir = getAndroidBaseDir(context);
if (!baseDir.exists()) {
baseDir.mkdir();
@@ -189,6 +197,31 @@ public BoxStoreBuilder androidContext(Object context) {
return this;
}
+ private Object getApplicationContext(Object context) {
+ try {
+ return context.getClass().getMethod("getApplicationContext").invoke(context);
+ } catch (Exception e) {
+ // note: can't catch ReflectiveOperationException, is K+ (19+) on Android
+ throw new RuntimeException("context must be a valid Android Context", e);
+ }
+ }
+
+ /**
+ * Pass a custom ReLinkerInstance, for example {@code ReLinker.log(logger)} to use for loading the native library
+ * on Android devices. Note that setting {@link #androidContext(Object)} is required for ReLinker to work.
+ */
+ public BoxStoreBuilder androidReLinker(Object reLinkerInstance) {
+ if (context == null) {
+ throw new IllegalArgumentException("Set a Context using androidContext(context) first");
+ }
+ //noinspection ConstantConditions Annotation does not enforce non-null.
+ if (reLinkerInstance == null) {
+ throw new NullPointerException("ReLinkerInstance may not be null");
+ }
+ this.relinker = reLinkerInstance;
+ return this;
+ }
+
static File getAndroidDbDir(Object context, @Nullable String dbName) {
File baseDir = getAndroidBaseDir(context);
return new File(baseDir, dbName(dbName));
@@ -227,7 +260,7 @@ private static File getAndroidFilesDir(Object context) {
}
/**
- * Sets the maximum number of concurrent readers. For most applications, the default is fine (> 100 readers).
+ * Sets the maximum number of concurrent readers. For most applications, the default is fine (> 100 readers).
*
* A "reader" is short for a thread involved in a read transaction.
*
@@ -243,7 +276,7 @@ public BoxStoreBuilder maxReaders(int maxReaders) {
}
@Internal
- public void entity(EntityInfo entityInfo) {
+ public void entity(EntityInfo> entityInfo) {
entityInfoList.add(entityInfo);
}
@@ -266,8 +299,10 @@ public BoxStoreBuilder maxSizeInKByte(long maxSizeInKByte) {
return this;
}
+ /**
+ * @deprecated Use {@link #debugFlags} instead.
+ */
@Deprecated
- /** @deprecated Use {@link #debugFlags} instead. */
public BoxStoreBuilder debugTransactions() {
this.debugFlags |= DebugFlags.LOG_TRANSACTIONS_READ | DebugFlags.LOG_TRANSACTIONS_WRITE;
return this;
@@ -312,7 +347,7 @@ public BoxStoreBuilder queryAttempts(int queryAttempts) {
* Useful for e.g. logging.
*/
@Experimental
- public BoxStoreBuilder failedReadTxAttemptCallback(TxCallback failedReadTxAttemptCallback) {
+ public BoxStoreBuilder failedReadTxAttemptCallback(TxCallback> failedReadTxAttemptCallback) {
this.failedReadTxAttemptCallback = failedReadTxAttemptCallback;
return this;
}
@@ -322,12 +357,7 @@ public BoxStoreBuilder failedReadTxAttemptCallback(TxCallback failedReadTxAttemp
*/
@Experimental
public BoxStoreBuilder initialDbFile(final File initialDbFile) {
- return initialDbFile(new Factory() {
- @Override
- public InputStream provide() throws FileNotFoundException {
- return new FileInputStream(initialDbFile);
- }
- });
+ return initialDbFile(() -> new FileInputStream(initialDbFile));
}
/**
diff --git a/objectbox-java/src/main/java/io/objectbox/Cursor.java b/objectbox-java/src/main/java/io/objectbox/Cursor.java
index 8f502596..abbcc58b 100644
--- a/objectbox-java/src/main/java/io/objectbox/Cursor.java
+++ b/objectbox-java/src/main/java/io/objectbox/Cursor.java
@@ -24,10 +24,10 @@
import io.objectbox.annotation.apihint.Beta;
import io.objectbox.annotation.apihint.Internal;
-import io.objectbox.annotation.apihint.Temporary;
+import io.objectbox.internal.CursorFactory;
import io.objectbox.relation.ToMany;
-@SuppressWarnings({"unchecked", "SameParameterValue", "unused"})
+@SuppressWarnings({"unchecked", "SameParameterValue", "unused", "WeakerAccess", "UnusedReturnValue"})
@Beta
@Internal
@NotThreadSafe
@@ -42,15 +42,15 @@ public abstract class Cursor implements Closeable {
protected static final int PUT_FLAG_FIRST = 1;
protected static final int PUT_FLAG_COMPLETE = 1 << 1;
- static native void nativeDestroy(long cursor);
+ native void nativeDestroy(long cursor);
- static native void nativeDeleteEntity(long cursor, long key);
+ static native boolean nativeDeleteEntity(long cursor, long key);
- static native void nativeDeleteAll(long cursor);
+ native void nativeDeleteAll(long cursor);
static native boolean nativeSeek(long cursor, long key);
- static native Object nativeGetAllEntities(long cursor);
+ native List nativeGetAllEntities(long cursor);
static native Object nativeGetEntity(long cursor, long key);
@@ -58,18 +58,11 @@ public abstract class Cursor implements Closeable {
static native Object nativeFirstEntity(long cursor);
- static native long nativeCount(long cursor);
-
- static native List nativeFindScalarPropertyId(long cursor, int propertyId, long value);
-
- static native List nativeFindStringPropertyId(long cursor, int propertyId, String value);
-
- // TODO not implemented
- static native long nativeGetKey(long cursor);
+ native long nativeCount(long cursor, long maxCountOrZero);
static native long nativeLookupKeyUsingIndex(long cursor, int propertyId, String value);
- static native long nativeRenew(long cursor);
+ native long nativeRenew(long cursor);
protected static native long collect313311(long cursor, long keyIfComplete, int flags,
int idStr1, @Nullable String valueStr1,
@@ -113,21 +106,27 @@ protected static native long collect004000(long cursor, long keyIfComplete, int
int idLong3, long valueLong3, int idLong4, long valueLong4
);
- static native int nativePropertyId(long cursor, String propertyValue);
+ native int nativePropertyId(long cursor, String propertyValue);
+
+ native List nativeGetBacklinkEntities(long cursor, int entityId, int propertyId, long key);
- static native List nativeGetBacklinkEntities(long cursor, int entityId, int propertyId, long key);
+ native long[] nativeGetBacklinkIds(long cursor, int entityId, int propertyId, long key);
- static native List nativeGetRelationEntities(long cursor, int sourceEntityId, int relationId, long key);
+ native List nativeGetRelationEntities(long cursor, int sourceEntityId, int relationId, long key, boolean backlink);
- static native void nativeModifyRelations(long cursor, int relationId, long key, long[] targetKeys, boolean remove);
+ native long[] nativeGetRelationIds(long cursor, int sourceEntityId, int relationId, long key, boolean backlink);
- static native void nativeModifyRelationsSingle(long cursor, int relationId, long key, long targetKey, boolean remove);
+ native void nativeModifyRelations(long cursor, int relationId, long key, long[] targetKeys, boolean remove);
- static native void nativeSetBoxStoreForEntities(long cursor, Object boxStore);
+ native void nativeModifyRelationsSingle(long cursor, int relationId, long key, long targetKey, boolean remove);
+
+ native void nativeSetBoxStoreForEntities(long cursor, Object boxStore);
+
+ native long nativeGetCursorFor(long cursor, int entityId);
protected final Transaction tx;
protected final long cursor;
- protected final EntityInfo entityInfo;
+ protected final EntityInfo entityInfo;
protected final BoxStore boxStoreForEntities;
protected final boolean readOnly;
@@ -135,7 +134,7 @@ protected static native long collect004000(long cursor, long keyIfComplete, int
private final Throwable creationThrowable;
- protected Cursor(Transaction tx, long cursor, EntityInfo entityInfo, BoxStore boxStore) {
+ protected Cursor(Transaction tx, long cursor, EntityInfo entityInfo, BoxStore boxStore) {
if (tx == null) {
throw new IllegalArgumentException("Transaction is null");
}
@@ -145,8 +144,8 @@ protected Cursor(Transaction tx, long cursor, EntityInfo entityInfo, BoxStore bo
this.entityInfo = entityInfo;
this.boxStoreForEntities = boxStore;
- Property[] allProperties = entityInfo.getAllProperties();
- for (Property property : allProperties) {
+ Property[] allProperties = entityInfo.getAllProperties();
+ for (Property property : allProperties) {
if (!property.isIdVerified()) {
int id = getPropertyId(property.dbName);
property.verifyId(id);
@@ -157,6 +156,10 @@ protected Cursor(Transaction tx, long cursor, EntityInfo entityInfo, BoxStore bo
nativeSetBoxStoreForEntities(cursor, boxStore);
}
+ /**
+ * Explicitly call {@link #close()} instead to avoid expensive finalization.
+ */
+ @SuppressWarnings("deprecation") // finalize()
@Override
protected void finalize() throws Throwable {
if (!closed) {
@@ -178,7 +181,7 @@ protected void finalize() throws Throwable {
public abstract long put(T entity);
- public EntityInfo getEntityInfo() {
+ public EntityInfo getEntityInfo() {
return entityInfo;
}
@@ -194,34 +197,31 @@ public T first() {
return (T) nativeFirstEntity(cursor);
}
- /** Does not work yet, also probably won't be faster than {@link Box#getAll()}. */
+ /** ~10% slower than iterating with {@link #first()} and {@link #next()} as done by {@link Box#getAll()}. */
public List getAll() {
- return (List) nativeGetAllEntities(cursor);
+ return nativeGetAllEntities(cursor);
}
- public void deleteEntity(long key) {
- nativeDeleteEntity(cursor, key);
+ public boolean deleteEntity(long key) {
+ return nativeDeleteEntity(cursor, key);
}
public void deleteAll() {
nativeDeleteAll(cursor);
}
- public long getKey() {
- return nativeGetKey(cursor);
- }
-
public boolean seek(long key) {
return nativeSeek(cursor, key);
}
- public long count() {
- return nativeCount(cursor);
+ public long count(long maxCountOrZero) {
+ return nativeCount(cursor, maxCountOrZero);
}
@Override
public synchronized void close() {
if (!closed) {
+ // Closeable recommendation: mark as closed before nativeDestroy could throw.
closed = true;
// tx is null despite check in constructor in some tests (called by finalizer):
// Null check avoids NPE in finalizer and seems to stabilize Android instrumentation perf tests.
@@ -235,16 +235,6 @@ public int getPropertyId(String propertyName) {
return nativePropertyId(cursor, propertyName);
}
- @Temporary
- public List find(Property property, long value) {
- return nativeFindScalarPropertyId(cursor, property.id, value);
- }
-
- @Temporary
- public List find(Property property, String value) {
- return nativeFindStringPropertyId(cursor, property.id, value);
- }
-
/**
* @return key or 0 if not found
* @deprecated TODO only used in tests, remove in the future
@@ -266,11 +256,16 @@ public boolean isClosed() {
return closed;
}
+ /**
+ * Note: this returns a secondary cursor, which does not survive standalone.
+ * Secondary native cursors are destroyed once their hosting Cursor is destroyed.
+ * Thus, use it only locally and don't store it long term.
+ */
protected Cursor getRelationTargetCursor(Class targetClass) {
- // minor to do: optimize by using existing native cursor handle?
- // (Note: Cursor should not destroy the native cursor then.)
-
- return tx.createCursor(targetClass);
+ EntityInfo entityInfo = boxStoreForEntities.getEntityInfo(targetClass);
+ long cursorHandle = nativeGetCursorFor(cursor, entityInfo.getEntityId());
+ CursorFactory factory = entityInfo.getCursorFactory();
+ return factory.createCursor(tx, cursorHandle, boxStoreForEntities);
}
/**
@@ -286,7 +281,7 @@ long internalHandle() {
}
@Internal
- List getBacklinkEntities(int entityId, Property relationIdProperty, long key) {
+ List getBacklinkEntities(int entityId, Property> relationIdProperty, long key) {
try {
return nativeGetBacklinkEntities(cursor, entityId, relationIdProperty.getId(), key);
} catch (IllegalArgumentException e) {
@@ -296,8 +291,23 @@ List getBacklinkEntities(int entityId, Property relationIdProperty, long key)
}
@Internal
- public List getRelationEntities(int sourceEntityId, int relationId, long key) {
- return nativeGetRelationEntities(cursor, sourceEntityId, relationId, key);
+ long[] getBacklinkIds(int entityId, Property> relationIdProperty, long key) {
+ try {
+ return nativeGetBacklinkIds(cursor, entityId, relationIdProperty.getId(), key);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Please check if the given property belongs to a valid @Relation: "
+ + relationIdProperty, e);
+ }
+ }
+
+ @Internal
+ public List getRelationEntities(int sourceEntityId, int relationId, long key, boolean backlink) {
+ return nativeGetRelationEntities(cursor, sourceEntityId, relationId, key, backlink);
+ }
+
+ @Internal
+ public long[] getRelationIds(int sourceEntityId, int relationId, long key, boolean backlink) {
+ return nativeGetRelationIds(cursor, sourceEntityId, relationId, key, backlink);
}
@Internal
@@ -314,11 +324,8 @@ protected void checkApplyToManyToDb(List orders, Class
if (orders instanceof ToMany) {
ToMany toMany = (ToMany) orders;
if (toMany.internalCheckApplyToDbRequired()) {
- Cursor targetCursor = getRelationTargetCursor(targetClass);
- try {
+ try (Cursor targetCursor = getRelationTargetCursor(targetClass)) {
toMany.internalApplyToDb(this, targetCursor);
- } finally {
- targetCursor.close();
}
}
}
diff --git a/objectbox-java/src/main/java/io/objectbox/DebugFlags.java b/objectbox-java/src/main/java/io/objectbox/DebugFlags.java
index 2fb4fd45..ebd95bd0 100644
--- a/objectbox-java/src/main/java/io/objectbox/DebugFlags.java
+++ b/objectbox-java/src/main/java/io/objectbox/DebugFlags.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,9 +27,8 @@ private DebugFlags() { }
public static final int LOG_TRANSACTIONS_WRITE = 2;
public static final int LOG_QUERIES = 4;
public static final int LOG_QUERY_PARAMETERS = 8;
-
- public static final String[] names = { "LOG_TRANSACTIONS_READ", "LOG_TRANSACTIONS_WRITE", "", "LOG_QUERIES", "", "", "", "LOG_QUERY_PARAMETERS", };
-
- public static String name(int e) { return names[e - LOG_TRANSACTIONS_READ]; }
+ public static final int LOG_ASYNC_QUEUE = 16;
+ public static final int LOG_CACHE_HITS = 32;
+ public static final int LOG_CACHE_ALL = 64;
}
diff --git a/objectbox-java/src/main/java/io/objectbox/EntityInfo.java b/objectbox-java/src/main/java/io/objectbox/EntityInfo.java
index 17d467a4..3b561fdd 100644
--- a/objectbox-java/src/main/java/io/objectbox/EntityInfo.java
+++ b/objectbox-java/src/main/java/io/objectbox/EntityInfo.java
@@ -32,9 +32,9 @@ public interface EntityInfo extends Serializable {
int getEntityId();
- Property[] getAllProperties();
+ Property[] getAllProperties();
- Property getIdProperty();
+ Property getIdProperty();
IdGetter getIdGetter();
diff --git a/objectbox-java/src/main/java/io/objectbox/Factory.java b/objectbox-java/src/main/java/io/objectbox/Factory.java
index ce91a07c..78020b23 100644
--- a/objectbox-java/src/main/java/io/objectbox/Factory.java
+++ b/objectbox-java/src/main/java/io/objectbox/Factory.java
@@ -19,10 +19,10 @@
import io.objectbox.annotation.apihint.Experimental;
-@Experimental
/**
* Generic Factory that provides a resource on demand (if and when it is required).
*/
+@Experimental
public interface Factory {
T provide() throws Exception;
}
diff --git a/objectbox-java/src/main/java/io/objectbox/KeyValueCursor.java b/objectbox-java/src/main/java/io/objectbox/KeyValueCursor.java
index 8fd53814..fec5b72a 100644
--- a/objectbox-java/src/main/java/io/objectbox/KeyValueCursor.java
+++ b/objectbox-java/src/main/java/io/objectbox/KeyValueCursor.java
@@ -21,6 +21,7 @@
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
+@SuppressWarnings("WeakerAccess,UnusedReturnValue, unused")
public class KeyValueCursor implements Closeable {
private static final int PUT_FLAG_FIRST = 1;
private static final int PUT_FLAG_COMPLETE = 1 << 1;
diff --git a/objectbox-java/src/main/java/io/objectbox/ModelBuilder.java b/objectbox-java/src/main/java/io/objectbox/ModelBuilder.java
index 378fa9ed..cc1efa41 100644
--- a/objectbox-java/src/main/java/io/objectbox/ModelBuilder.java
+++ b/objectbox-java/src/main/java/io/objectbox/ModelBuilder.java
@@ -21,6 +21,8 @@
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.Nullable;
+
import io.objectbox.annotation.apihint.Internal;
import io.objectbox.model.IdUid;
import io.objectbox.model.Model;
@@ -29,6 +31,7 @@
import io.objectbox.model.ModelRelation;
// Remember: IdUid is a struct, not a table, and thus must be inlined
+@SuppressWarnings("WeakerAccess,UnusedReturnValue, unused")
@Internal
public class ModelBuilder {
private static final int MODEL_VERSION = 2;
@@ -60,8 +63,9 @@ public class PropertyBuilder {
private long uid;
private int indexId;
private long indexUid;
+ private int indexMaxValueLength;
- PropertyBuilder(String name, String targetEntityName, String virtualTarget, int type) {
+ PropertyBuilder(String name, @Nullable String targetEntityName, @Nullable String virtualTarget, int type) {
this.type = type;
propertyNameOffset = fbb.createString(name);
targetEntityOffset = targetEntityName != null ? fbb.createString(targetEntityName) : 0;
@@ -82,6 +86,12 @@ public PropertyBuilder indexId(int indexId, long indexUid) {
return this;
}
+ public PropertyBuilder indexMaxValueLength(int indexMaxValueLength) {
+ checkNotFinished();
+ this.indexMaxValueLength = indexMaxValueLength;
+ return this;
+ }
+
public PropertyBuilder flags(int flags) {
checkNotFinished();
this.flags = flags;
@@ -90,9 +100,7 @@ public PropertyBuilder flags(int flags) {
public PropertyBuilder secondaryName(String secondaryName) {
checkNotFinished();
- if (secondaryName != null) {
- secondaryNameOffset = fbb.createString(secondaryName);
- }
+ secondaryNameOffset = fbb.createString(secondaryName);
return this;
}
@@ -124,6 +132,9 @@ public int finish() {
int indexIdOffset = IdUid.createIdUid(fbb, indexId, indexUid);
ModelProperty.addIndexId(fbb, indexIdOffset);
}
+ if (indexMaxValueLength > 0) {
+ ModelProperty.addMaxIndexValueLength(fbb, indexMaxValueLength);
+ }
ModelProperty.addType(fbb, type);
if (flags != 0) {
ModelProperty.addFlags(fbb, flags);
@@ -178,11 +189,12 @@ public PropertyBuilder property(String name, int type) {
return property(name, null, type);
}
- public PropertyBuilder property(String name, String targetEntityName, int type) {
+ public PropertyBuilder property(String name, @Nullable String targetEntityName, int type) {
return property(name, targetEntityName, null, type);
}
- public PropertyBuilder property(String name, String targetEntityName, String virtualTarget, int type) {
+ public PropertyBuilder property(String name, @Nullable String targetEntityName, @Nullable String virtualTarget,
+ int type) {
checkNotFinished();
checkFinishProperty();
propertyBuilder = new PropertyBuilder(name, targetEntityName, virtualTarget, type);
@@ -226,7 +238,7 @@ public ModelBuilder entityDone() {
ModelEntity.addName(fbb, testEntityNameOffset);
ModelEntity.addProperties(fbb, propertiesOffset);
if (relationsOffset != 0) ModelEntity.addRelations(fbb, relationsOffset);
- if (id != null || uid != null) {
+ if (id != null && uid != null) {
int idOffset = IdUid.createIdUid(fbb, id, uid);
ModelEntity.addId(fbb, idOffset);
}
diff --git a/objectbox-java/src/main/java/io/objectbox/ObjectClassPublisher.java b/objectbox-java/src/main/java/io/objectbox/ObjectClassPublisher.java
index 25ad301d..9adc0c41 100644
--- a/objectbox-java/src/main/java/io/objectbox/ObjectClassPublisher.java
+++ b/objectbox-java/src/main/java/io/objectbox/ObjectClassPublisher.java
@@ -47,11 +47,11 @@ class ObjectClassPublisher implements DataPublisher, Runnable {
public void subscribe(DataObserver observer, @Nullable Object forClass) {
if (forClass == null) {
for (int entityTypeId : boxStore.getAllEntityTypeIds()) {
- observersByEntityTypeId.putElement(entityTypeId, (DataObserver) observer);
+ observersByEntityTypeId.putElement(entityTypeId, observer);
}
} else {
- int entityTypeId = boxStore.getEntityTypeIdOrThrow((Class) forClass);
- observersByEntityTypeId.putElement(entityTypeId, (DataObserver) observer);
+ int entityTypeId = boxStore.getEntityTypeIdOrThrow((Class>) forClass);
+ observersByEntityTypeId.putElement(entityTypeId, observer);
}
}
@@ -61,7 +61,7 @@ public void subscribe(DataObserver observer, @Nullable Object forClass) {
*/
public void unsubscribe(DataObserver observer, @Nullable Object forClass) {
if (forClass != null) {
- int entityTypeId = boxStore.getEntityTypeIdOrThrow((Class) forClass);
+ int entityTypeId = boxStore.getEntityTypeIdOrThrow((Class>) forClass);
unsubscribe(observer, entityTypeId);
} else {
for (int entityTypeId : boxStore.getAllEntityTypeIds()) {
@@ -77,17 +77,14 @@ private void unsubscribe(DataObserver observer, int entityTypeId) {
@Override
public void publishSingle(final DataObserver observer, @Nullable final Object forClass) {
- boxStore.internalScheduleThread(new Runnable() {
- @Override
- public void run() {
- Collection entityClasses = forClass != null ? Collections.singletonList((Class) forClass) :
- boxStore.getAllEntityClasses();
- for (Class entityClass : entityClasses) {
- try {
- observer.onData(entityClass);
- } catch (RuntimeException e) {
- handleObserverException(entityClass);
- }
+ boxStore.internalScheduleThread(() -> {
+ Collection> entityClasses = forClass != null ? Collections.singletonList((Class>) forClass) :
+ boxStore.getAllEntityClasses();
+ for (Class> entityClass : entityClasses) {
+ try {
+ observer.onData(entityClass);
+ } catch (RuntimeException e) {
+ handleObserverException(entityClass);
}
}
});
@@ -132,7 +129,7 @@ public void run() {
for (int entityTypeId : entityTypeIdsAffected) {
Collection> observers = observersByEntityTypeId.get(entityTypeId);
if (observers != null && !observers.isEmpty()) {
- Class objectClass = boxStore.getEntityClassOrThrow(entityTypeId);
+ Class> objectClass = boxStore.getEntityClassOrThrow(entityTypeId);
try {
for (DataObserver observer : observers) {
observer.onData(objectClass);
diff --git a/objectbox-java/src/main/java/io/objectbox/Property.java b/objectbox-java/src/main/java/io/objectbox/Property.java
index e1aee625..af7ff7cb 100644
--- a/objectbox-java/src/main/java/io/objectbox/Property.java
+++ b/objectbox-java/src/main/java/io/objectbox/Property.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2017-2019 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
import java.io.Serializable;
import java.util.Collection;
+import javax.annotation.Nullable;
+
import io.objectbox.annotation.apihint.Internal;
import io.objectbox.converter.PropertyConverter;
import io.objectbox.exception.DbException;
@@ -27,11 +29,14 @@
import io.objectbox.query.QueryCondition.PropertyCondition.Operation;
/**
- * Meta data describing a property
+ * Meta data describing a property of an ObjectBox entity.
+ * Properties are typically used to define query criteria using {@link io.objectbox.query.QueryBuilder}.
*/
-public class Property implements Serializable {
+@SuppressWarnings("WeakerAccess,UnusedReturnValue, unused")
+public class Property implements Serializable {
private static final long serialVersionUID = 8613291105982758093L;
+ public final EntityInfo entity;
public final int ordinal;
public final int id;
@@ -39,79 +44,95 @@ public class Property implements Serializable {
public final Class> type;
public final String name;
- public final boolean primaryKey;
+ public final boolean isId;
+ public final boolean isVirtual;
public final String dbName;
- public final Class extends PropertyConverter> converterClass;
+ public final Class extends PropertyConverter, ?>> converterClass;
/** Type, which is converted to a type supported by the DB. */
- public final Class customType;
+ public final Class> customType;
// TODO verified state should be per DB -> move to BoxStore/Box.
// Also, this should make the Property class truly @Immutable.
private boolean idVerified;
- public Property(int ordinal, int id, Class> type, String name, boolean primaryKey, String dbName) {
- this(ordinal, id, type, name, primaryKey, dbName, null, null);
+ public Property(EntityInfo entity, int ordinal, int id, Class> type, String name) {
+ this(entity, ordinal, id, type, name, false, name, null, null);
+ }
+
+ public Property(EntityInfo entity, int ordinal, int id, Class> type, String name, boolean isVirtual) {
+ this(entity, ordinal, id, type, name, false, isVirtual, name, null, null);
+ }
+
+ public Property(EntityInfo entity, int ordinal, int id, Class> type, String name, boolean isId,
+ @Nullable String dbName) {
+ this(entity, ordinal, id, type, name, isId, dbName, null, null);
}
- public Property(int ordinal, int id, Class> type, String name) {
- this(ordinal, id, type, name, false, name, null, null);
+ // Note: types of PropertyConverter might not exactly match type and customtype, e.g. if using generics like List.class.
+ public Property(EntityInfo entity, int ordinal, int id, Class> type, String name, boolean isId,
+ @Nullable String dbName, @Nullable Class extends PropertyConverter, ?>> converterClass,
+ @Nullable Class> customType) {
+ this(entity, ordinal, id, type, name, isId, false, dbName, converterClass, customType);
}
- public Property(int ordinal, int id, Class> type, String name, boolean primaryKey, String dbName,
- Class extends PropertyConverter> converterClass, Class customType) {
+ public Property(EntityInfo entity, int ordinal, int id, Class> type, String name, boolean isId,
+ boolean isVirtual, @Nullable String dbName,
+ @Nullable Class extends PropertyConverter, ?>> converterClass, @Nullable Class> customType) {
+ this.entity = entity;
this.ordinal = ordinal;
this.id = id;
this.type = type;
this.name = name;
- this.primaryKey = primaryKey;
+ this.isId = isId;
+ this.isVirtual = isVirtual;
this.dbName = dbName;
this.converterClass = converterClass;
this.customType = customType;
}
- /** Creates an "equal ('=')" condition for this property. */
+ /** Creates an "equal ('=')" condition for this property. */
public QueryCondition eq(Object value) {
return new PropertyCondition(this, Operation.EQUALS, value);
}
- /** Creates an "not equal ('<>')" condition for this property. */
+ /** Creates an "not equal ('<>')" condition for this property. */
public QueryCondition notEq(Object value) {
return new PropertyCondition(this, Operation.NOT_EQUALS, value);
}
- /** Creates an "BETWEEN ... AND ..." condition for this property. */
+ /** Creates an "BETWEEN ... AND ..." condition for this property. */
public QueryCondition between(Object value1, Object value2) {
Object[] values = {value1, value2};
return new PropertyCondition(this, Operation.BETWEEN, values);
}
- /** Creates an "IN (..., ..., ...)" condition for this property. */
+ /** Creates an "IN (..., ..., ...)" condition for this property. */
public QueryCondition in(Object... inValues) {
return new PropertyCondition(this, Operation.IN, inValues);
}
- /** Creates an "IN (..., ..., ...)" condition for this property. */
+ /** Creates an "IN (..., ..., ...)" condition for this property. */
public QueryCondition in(Collection> inValues) {
return in(inValues.toArray());
}
- /** Creates an "greater than ('>')" condition for this property. */
+ /** Creates an "greater than ('>')" condition for this property. */
public QueryCondition gt(Object value) {
return new PropertyCondition(this, Operation.GREATER_THAN, value);
}
- /** Creates an "less than ('<')" condition for this property. */
+ /** Creates an "less than ('<')" condition for this property. */
public QueryCondition lt(Object value) {
return new PropertyCondition(this, Operation.LESS_THAN, value);
}
- /** Creates an "IS NULL" condition for this property. */
+ /** Creates an "IS NULL" condition for this property. */
public QueryCondition isNull() {
return new PropertyCondition(this, Operation.IS_NULL, null);
}
- /** Creates an "IS NOT NULL" condition for this property. */
+ /** Creates an "IS NOT NULL" condition for this property. */
public QueryCondition isNotNull() {
return new PropertyCondition(this, Operation.IS_NOT_NULL, null);
}
@@ -137,6 +158,11 @@ public QueryCondition endsWith(String value) {
return new PropertyCondition(this, Operation.ENDS_WITH, value);
}
+ @Internal
+ public int getEntityId() {
+ return entity.getEntityId();
+ }
+
@Internal
public int getId() {
if (this.id <= 0) {
diff --git a/objectbox-java/src/main/java/io/objectbox/Transaction.java b/objectbox-java/src/main/java/io/objectbox/Transaction.java
index 63e0d819..888317cb 100644
--- a/objectbox-java/src/main/java/io/objectbox/Transaction.java
+++ b/objectbox-java/src/main/java/io/objectbox/Transaction.java
@@ -26,6 +26,7 @@
@Internal
@NotThreadSafe
+@SuppressWarnings("WeakerAccess,UnusedReturnValue,unused")
public class Transaction implements Closeable {
/** May be set by tests */
@Internal
@@ -41,29 +42,31 @@ public class Transaction implements Closeable {
/** volatile because finalizer thread may interfere with "one thread, one TX" rule */
private volatile boolean closed;
- static native void nativeDestroy(long transaction);
+ native void nativeDestroy(long transaction);
- static native int[] nativeCommit(long transaction);
+ native int[] nativeCommit(long transaction);
- static native void nativeAbort(long transaction);
+ native void nativeAbort(long transaction);
- static native void nativeReset(long transaction);
+ native void nativeReset(long transaction);
- static native void nativeRecycle(long transaction);
+ native void nativeRecycle(long transaction);
- static native void nativeRenew(long transaction);
+ native void nativeRenew(long transaction);
- static native long nativeCreateKeyValueCursor(long transaction);
+ native long nativeCreateKeyValueCursor(long transaction);
- static native long nativeCreateCursor(long transaction, String entityName, Class entityClass);
+ native long nativeCreateCursor(long transaction, String entityName, Class> entityClass);
- //static native long nativeGetStore(long transaction);
+ // native long nativeGetStore(long transaction);
- static native boolean nativeIsActive(long transaction);
+ native boolean nativeIsActive(long transaction);
- static native boolean nativeIsRecycled(long transaction);
+ native boolean nativeIsOwnerThread(long transaction);
- static native boolean nativeIsReadOnly(long transaction);
+ native boolean nativeIsRecycled(long transaction);
+
+ native boolean nativeIsReadOnly(long transaction);
public Transaction(BoxStore store, long transaction, int initialCommitCount) {
this.store = store;
@@ -74,17 +77,12 @@ public Transaction(BoxStore store, long transaction, int initialCommitCount) {
creationThrowable = TRACK_CREATION_STACK ? new Throwable() : null;
}
+ /**
+ * Explicitly call {@link #close()} instead to avoid expensive finalization.
+ */
+ @SuppressWarnings("deprecation") // finalize()
@Override
protected void finalize() throws Throwable {
- // Committed & aborted transactions are fine: remaining native resources are not expensive
- if (!closed && nativeIsActive(transaction)) { // TODO what about recycled state?
- System.err.println("Transaction was not finished (initial commit count: " + initialCommitCount + ").");
- if (creationThrowable != null) {
- System.err.println("Transaction was initially created here:");
- creationThrowable.printStackTrace();
- }
- System.err.flush();
- }
close();
super.finalize();
}
@@ -98,9 +96,31 @@ private void checkOpen() {
@Override
public synchronized void close() {
if (!closed) {
+ // Closeable recommendation: mark as closed before any code that might throw.
closed = true;
store.unregisterTransaction(this);
+ if (!nativeIsOwnerThread(transaction)) {
+ boolean isActive = nativeIsActive(transaction);
+ boolean isRecycled = nativeIsRecycled(transaction);
+ if (isActive || isRecycled) {
+ String msgPostfix = " (initial commit count: " + initialCommitCount + ").";
+ if (isActive) {
+ System.err.println("Transaction is still active" + msgPostfix);
+ } else {
+ // This is not uncommon when using Box; as it keeps a thread-local Cursor and recycles the TX
+ System.out.println("Hint: use closeThreadResources() to avoid finalizing recycled transactions"
+ + msgPostfix);
+ System.out.flush();
+ }
+ if (creationThrowable != null) {
+ System.err.println("Transaction was initially created here:");
+ creationThrowable.printStackTrace();
+ }
+ System.err.flush();
+ }
+ }
+
// If store is already closed natively, destroying the tx would cause EXCEPTION_ACCESS_VIOLATION
// TODO not destroying is probably only a small leak on rare occasions, but still could be fixed
if (!store.isClosed()) {
@@ -160,7 +180,7 @@ public KeyValueCursor createKeyValueCursor() {
public Cursor createCursor(Class entityClass) {
checkOpen();
- EntityInfo entityInfo = store.getEntityInfo(entityClass);
+ EntityInfo entityInfo = store.getEntityInfo(entityClass);
CursorFactory factory = entityInfo.getCursorFactory();
long cursorHandle = nativeCreateCursor(transaction, entityInfo.getDbName(), entityClass);
return factory.createCursor(this, cursorHandle, store);
diff --git a/objectbox-java/src/main/java/io/objectbox/converter/NullToEmptyStringConverter.java b/objectbox-java/src/main/java/io/objectbox/converter/NullToEmptyStringConverter.java
new file mode 100644
index 00000000..1f8873fd
--- /dev/null
+++ b/objectbox-java/src/main/java/io/objectbox/converter/NullToEmptyStringConverter.java
@@ -0,0 +1,22 @@
+package io.objectbox.converter;
+
+import javax.annotation.Nullable;
+
+/**
+ * Used as a converter if a property is annotated with {@link io.objectbox.annotation.DefaultValue @DefaultValue("")}.
+ */
+public class NullToEmptyStringConverter implements PropertyConverter {
+
+ @Override
+ public String convertToDatabaseValue(String entityProperty) {
+ return entityProperty;
+ }
+
+ @Override
+ public String convertToEntityProperty(@Nullable String databaseValue) {
+ if (databaseValue == null) {
+ return "";
+ }
+ return databaseValue;
+ }
+}
diff --git a/objectbox-java-api/src/main/java/io/objectbox/annotation/JoinProperty.java b/objectbox-java/src/main/java/io/objectbox/exception/ConstraintViolationException.java
similarity index 52%
rename from objectbox-java-api/src/main/java/io/objectbox/annotation/JoinProperty.java
rename to objectbox-java/src/main/java/io/objectbox/exception/ConstraintViolationException.java
index 6fe3b46e..29088db7 100644
--- a/objectbox-java-api/src/main/java/io/objectbox/annotation/JoinProperty.java
+++ b/objectbox-java/src/main/java/io/objectbox/exception/ConstraintViolationException.java
@@ -14,23 +14,11 @@
* limitations under the License.
*/
-package io.objectbox.annotation;
+package io.objectbox.exception;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Defines name and referencedName properties for relations
- *
- * @see Relation
- */
-@Retention(RetentionPolicy.CLASS)
-@Target({})
-/** TODO public */ @interface JoinProperty {
- /** Name of the property in the name entity, which matches {@link #referencedName()} */
- String name();
-
- /** Name of the property in the referencedName entity, which matches {@link #name()} */
- String referencedName();
+/** Base class for exceptions thrown when a constraint would be violated during a put operation. */
+public class ConstraintViolationException extends DbException {
+ public ConstraintViolationException(String message) {
+ super(message);
+ }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/exception/DbMaxReadersExceededException.java b/objectbox-java/src/main/java/io/objectbox/exception/DbMaxReadersExceededException.java
index c1317fdb..d3587778 100644
--- a/objectbox-java/src/main/java/io/objectbox/exception/DbMaxReadersExceededException.java
+++ b/objectbox-java/src/main/java/io/objectbox/exception/DbMaxReadersExceededException.java
@@ -23,7 +23,7 @@
* Thrown when the maximum of readers (read transactions) was exceeded.
* Verify that you run a reasonable amount of threads only.
*
- * If you intend to work with a very high number of threads (>100), consider increasing the number of maximum readers
+ * If you intend to work with a very high number of threads (>100), consider increasing the number of maximum readers
* using {@link BoxStoreBuilder#maxReaders(int)} and enabling query retries using
* {@link BoxStoreBuilder#queryAttempts(int)}.
*
diff --git a/objectbox-java/src/main/java/io/objectbox/exception/NonUniqueResultException.java b/objectbox-java/src/main/java/io/objectbox/exception/NonUniqueResultException.java
new file mode 100644
index 00000000..4eb4dbdf
--- /dev/null
+++ b/objectbox-java/src/main/java/io/objectbox/exception/NonUniqueResultException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2018 ObjectBox Ltd. All rights reserved.
+ *
+ * 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 io.objectbox.exception;
+
+/** Throw if {@link io.objectbox.query.Query#findUnique()} returns more than one result. */
+public class NonUniqueResultException extends DbException {
+ public NonUniqueResultException(String message) {
+ super(message);
+ }
+}
diff --git a/objectbox-java/src/main/java/io/objectbox/exception/NumericOverflowException.java b/objectbox-java/src/main/java/io/objectbox/exception/NumericOverflowException.java
new file mode 100644
index 00000000..8ab0c395
--- /dev/null
+++ b/objectbox-java/src/main/java/io/objectbox/exception/NumericOverflowException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 ObjectBox Ltd. All rights reserved.
+ *
+ * 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 io.objectbox.exception;
+
+/**
+ * Thrown if a property query aggregate function can not compute a result due to a number type overflowing.
+ */
+public class NumericOverflowException extends DbException {
+
+ public NumericOverflowException(String message) {
+ super(message);
+ }
+}
diff --git a/objectbox-java/src/main/java/io/objectbox/exception/UniqueViolationException.java b/objectbox-java/src/main/java/io/objectbox/exception/UniqueViolationException.java
new file mode 100644
index 00000000..023bbbac
--- /dev/null
+++ b/objectbox-java/src/main/java/io/objectbox/exception/UniqueViolationException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017-2018 ObjectBox Ltd. All rights reserved.
+ *
+ * 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 io.objectbox.exception;
+
+/** Thrown when a @{@link io.objectbox.annotation.Unique} constraint would be violated during a put operation. */
+public class UniqueViolationException extends ConstraintViolationException {
+ public UniqueViolationException(String message) {
+ super(message);
+ }
+}
diff --git a/objectbox-java/src/main/java/io/objectbox/exception/package-info.java b/objectbox-java/src/main/java/io/objectbox/exception/package-info.java
new file mode 100644
index 00000000..c389e4c5
--- /dev/null
+++ b/objectbox-java/src/main/java/io/objectbox/exception/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019 ObjectBox Ltd. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * Various exceptions thrown by ObjectBox.
+ */
+package io.objectbox.exception;
\ No newline at end of file
diff --git a/objectbox-java/src/main/java/io/objectbox/internal/DebugCursor.java b/objectbox-java/src/main/java/io/objectbox/internal/DebugCursor.java
index 16f09a7b..88816ab0 100644
--- a/objectbox-java/src/main/java/io/objectbox/internal/DebugCursor.java
+++ b/objectbox-java/src/main/java/io/objectbox/internal/DebugCursor.java
@@ -51,6 +51,7 @@ public DebugCursor(Transaction tx, long handle) {
@Override
public synchronized void close() {
if (!closed) {
+ // Closeable recommendation: mark as closed before any code that might throw.
closed = true;
// tx is null despite check in constructor in some tests (called by finalizer):
// Null check avoids NPE in finalizer and seems to stabilize Android instrumentation perf tests.
@@ -60,6 +61,10 @@ public synchronized void close() {
}
}
+ /**
+ * Explicitly call {@link #close()} instead to avoid expensive finalization.
+ */
+ @SuppressWarnings("deprecation") // finalize()
@Override
protected void finalize() throws Throwable {
if (!closed) {
diff --git a/objectbox-java/src/main/java/io/objectbox/internal/NativeLibraryLoader.java b/objectbox-java/src/main/java/io/objectbox/internal/NativeLibraryLoader.java
index 744a9726..39afb44a 100644
--- a/objectbox-java/src/main/java/io/objectbox/internal/NativeLibraryLoader.java
+++ b/objectbox-java/src/main/java/io/objectbox/internal/NativeLibraryLoader.java
@@ -25,23 +25,39 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
+import io.objectbox.BoxStore;
+
/**
* Separate class, so we can mock BoxStore.
*/
public class NativeLibraryLoader {
+
+ private static final String OBJECTBOX_JNI = "objectbox-jni";
+
static {
- String libname = "objectbox";
- String filename = "objectbox.so";
+ String libname = OBJECTBOX_JNI;
+ String filename = libname + ".so";
+
+ final String vendor = System.getProperty("java.vendor");
+ final String osName = System.getProperty("os.name").toLowerCase();
+
+ // Some Android devices are detected as neither Android or Linux below,
+ // so assume Linux by default to always fallback to Android
+ boolean isLinux = true;
// For Android, os.name is also "Linux", so we need an extra check
- boolean android = System.getProperty("java.vendor").contains("Android");
+ // Is not completely reliable (e.g. Vivo devices), see workaround on load failure
+ // Note: can not use check for Android classes as testing frameworks (Robolectric)
+ // may provide them on non-Android devices
+ final boolean android = vendor.contains("Android");
if (!android) {
- String osName = System.getProperty("os.name").toLowerCase();
- String sunArch = System.getProperty("sun.arch.data.model");
- String cpuArchPostfix = "32".equals(sunArch) ? "-x86" : "-x64";
+ String cpuArchPostfix = "-" + getCpuArch();
if (osName.contains("windows")) {
+ isLinux = false;
libname += "-windows" + cpuArchPostfix;
filename = libname + ".dll";
checkUnpackLib(filename);
@@ -50,20 +66,85 @@ public class NativeLibraryLoader {
filename = "lib" + libname + ".so";
checkUnpackLib(filename);
} else if (osName.contains("mac")) {
+ isLinux = false;
libname += "-macos" + cpuArchPostfix;
filename = "lib" + libname + ".dylib";
checkUnpackLib(filename);
}
}
- File file = new File(filename);
- if (file.exists()) {
- System.load(file.getAbsolutePath());
- } else {
- if (!android) {
- System.err.println("File not available: " + file.getAbsolutePath());
+ try {
+ File file = new File(filename);
+ if (file.exists()) {
+ System.load(file.getAbsolutePath());
+ } else {
+ try {
+ if (android) {
+ boolean success = loadLibraryAndroid();
+ if (!success) {
+ System.loadLibrary(libname);
+ }
+ } else {
+ System.err.println("File not available: " + file.getAbsolutePath());
+ System.loadLibrary(libname);
+ }
+ } catch (UnsatisfiedLinkError e) {
+ if (!android && isLinux) {
+ // maybe is Android, but check failed: try loading Android lib
+ boolean success = loadLibraryAndroid();
+ if (!success) {
+ System.loadLibrary(OBJECTBOX_JNI);
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ } catch (UnsatisfiedLinkError e) {
+ String osArch = System.getProperty("os.arch");
+ String sunArch = System.getProperty("sun.arch.data.model");
+ String message = String.format(
+ "Loading ObjectBox native library failed: vendor=%s,os=%s,os.arch=%s,sun.arch=%s,android=%s,linux=%s",
+ vendor, osName, osArch, sunArch, android, isLinux
+ );
+ throw new LinkageError(message, e); // UnsatisfiedLinkError does not allow a cause; use its super class
+ }
+ }
+
+ private static String getCpuArch() {
+ String osArch = System.getProperty("os.arch");
+ String cpuArch = null;
+ if (osArch != null) {
+ osArch = osArch.toLowerCase();
+ if (osArch.equalsIgnoreCase("amd64") || osArch.equalsIgnoreCase("x86_64")) {
+ cpuArch = "x64";
+ } else if (osArch.equalsIgnoreCase("x86")) {
+ cpuArch = "x86";
+ } else if (osArch.startsWith("arm")) {
+ switch (osArch) {
+ case "armv7":
+ case "armv7l":
+ case "armeabi-v7a": // os.arch "armeabi-v7a" might be Android only, but let's try anyway...
+ cpuArch = "armv7";
+ break;
+ case "arm64-v8a":
+ cpuArch = "arm64";
+ break;
+ case "armv6":
+ cpuArch = "armv6";
+ break;
+ default:
+ cpuArch = "armv6"; // Lowest version we support
+ System.err.println("Unknown os.arch \"" + osArch + "\" - ObjectBox is defaulting to " + cpuArch);
+ break;
+ }
}
- System.loadLibrary(libname);
}
+ if (cpuArch == null) {
+ String sunArch = System.getProperty("sun.arch.data.model");
+ cpuArch = "32".equals(sunArch) ? "x86" : "x64";
+ System.err.println("Unknown os.arch \"" + osArch + "\" - ObjectBox is defaulting to " + cpuArch);
+ }
+ return cpuArch;
}
private static void checkUnpackLib(String filename) {
@@ -99,6 +180,39 @@ private static void checkUnpackLib(String filename) {
}
}
+ private static boolean loadLibraryAndroid() {
+ if (BoxStore.getContext() == null) {
+ return false;
+ }
+
+ //noinspection TryWithIdenticalCatches
+ try {
+ Class> context = Class.forName("android.content.Context");
+ if (BoxStore.getRelinker() == null) {
+ // use default ReLinker
+ Class> relinker = Class.forName("com.getkeepsafe.relinker.ReLinker");
+ Method loadLibrary = relinker.getMethod("loadLibrary", context, String.class, String.class);
+ loadLibrary.invoke(null, BoxStore.getContext(), OBJECTBOX_JNI, BoxStore.JNI_VERSION);
+ } else {
+ // use custom ReLinkerInstance
+ Method loadLibrary = BoxStore.getRelinker().getClass().getMethod("loadLibrary", context, String.class, String.class);
+ loadLibrary.invoke(BoxStore.getRelinker(), BoxStore.getContext(), OBJECTBOX_JNI, BoxStore.JNI_VERSION);
+ }
+ } catch (NoSuchMethodException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return false;
+ } catch (InvocationTargetException e) {
+ return false;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ // note: do not catch Exception as it will swallow ReLinker exceptions useful for debugging
+ // note: can't catch ReflectiveOperationException, is K+ (19+) on Android
+
+ return true;
+ }
+
public static void ensureLoaded() {
}
}
diff --git a/objectbox-java/src/main/java/io/objectbox/internal/ObjectBoxThreadPool.java b/objectbox-java/src/main/java/io/objectbox/internal/ObjectBoxThreadPool.java
index f84efb99..41b2ccdd 100644
--- a/objectbox-java/src/main/java/io/objectbox/internal/ObjectBoxThreadPool.java
+++ b/objectbox-java/src/main/java/io/objectbox/internal/ObjectBoxThreadPool.java
@@ -40,7 +40,7 @@ public class ObjectBoxThreadPool extends ThreadPoolExecutor {
private final BoxStore boxStore;
public ObjectBoxThreadPool(BoxStore boxStore) {
- super(0, Integer.MAX_VALUE, 20L, TimeUnit.SECONDS, new SynchronousQueue(),
+ super(0, Integer.MAX_VALUE, 20L, TimeUnit.SECONDS, new SynchronousQueue<>(),
new ObjectBoxThreadFactory());
this.boxStore = boxStore;
}
diff --git a/objectbox-java/src/main/java/io/objectbox/internal/ReflectionCache.java b/objectbox-java/src/main/java/io/objectbox/internal/ReflectionCache.java
index 32fbb9e7..3176431c 100644
--- a/objectbox-java/src/main/java/io/objectbox/internal/ReflectionCache.java
+++ b/objectbox-java/src/main/java/io/objectbox/internal/ReflectionCache.java
@@ -32,10 +32,10 @@ public static ReflectionCache getInstance() {
return instance;
}
- private final Map> fields = new HashMap<>();
+ private final Map, Map> fields = new HashMap<>();
@Nonnull
- public synchronized Field getField(Class clazz, String name) {
+ public synchronized Field getField(Class> clazz, String name) {
Map fieldsForClass = fields.get(clazz);
if (fieldsForClass == null) {
fieldsForClass = new HashMap<>();
diff --git a/objectbox-java/src/main/java/io/objectbox/model/EntityFlags.java b/objectbox-java/src/main/java/io/objectbox/model/EntityFlags.java
index 6607726d..d6e8909c 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/EntityFlags.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/EntityFlags.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,8 @@ private EntityFlags() { }
*/
public static final int USE_NO_ARG_CONSTRUCTOR = 1;
- public static final String[] names = { "USE_NO_ARG_CONSTRUCTOR", };
+ // Private to protect contents from getting modified.
+ private static final String[] names = { "USE_NO_ARG_CONSTRUCTOR", };
public static String name(int e) { return names[e - USE_NO_ARG_CONSTRUCTOR]; }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/IdUid.java b/objectbox-java/src/main/java/io/objectbox/model/IdUid.java
index f5e18f47..9882f3b4 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/IdUid.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/IdUid.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
* ID tuple: besides the main ID there is also a UID for verification
*/
public final class IdUid extends Struct {
- public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
+ public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }
public IdUid __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public long id() { return (long)bb.getInt(bb_pos + 0) & 0xFFFFFFFFL; }
@@ -45,5 +45,12 @@ public static int createIdUid(FlatBufferBuilder builder, long id, long uid) {
builder.putInt((int)id);
return builder.offset();
}
+
+ public static final class Vector extends BaseVector {
+ public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; }
+
+ public IdUid get(int j) { return get(new IdUid(), j); }
+ public IdUid get(IdUid obj, int j) { return obj.__assign(__element(j), bb); }
+ }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/Model.java b/objectbox-java/src/main/java/io/objectbox/model/Model.java
index 62a38d2f..57e258fe 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/Model.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/Model.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,9 +31,10 @@
* There could be multiple models/schemas (one dbi per schema) in the future.
*/
public final class Model extends Table {
+ public static void ValidateVersion() { Constants.FLATBUFFERS_1_12_0(); }
public static Model getRootAsModel(ByteBuffer _bb) { return getRootAsModel(_bb, new Model()); }
public static Model getRootAsModel(ByteBuffer _bb, Model obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
- public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
+ public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }
public Model __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
/**
@@ -45,23 +46,26 @@ public final class Model extends Table {
*/
public String name() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(6, 1); }
+ public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); }
/**
* User controlled version, not really used at the moment
*/
public long version() { int o = __offset(8); return o != 0 ? bb.getLong(o + bb_pos) : 0L; }
- public ModelEntity entities(int j) { return entities(new ModelEntity(), j); }
- public ModelEntity entities(ModelEntity obj, int j) { int o = __offset(10); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
+ public io.objectbox.model.ModelEntity entities(int j) { return entities(new io.objectbox.model.ModelEntity(), j); }
+ public io.objectbox.model.ModelEntity entities(io.objectbox.model.ModelEntity obj, int j) { int o = __offset(10); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
public int entitiesLength() { int o = __offset(10); return o != 0 ? __vector_len(o) : 0; }
- public IdUid lastEntityId() { return lastEntityId(new IdUid()); }
- public IdUid lastEntityId(IdUid obj) { int o = __offset(12); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
- public IdUid lastIndexId() { return lastIndexId(new IdUid()); }
- public IdUid lastIndexId(IdUid obj) { int o = __offset(14); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
- public IdUid lastSequenceId() { return lastSequenceId(new IdUid()); }
- public IdUid lastSequenceId(IdUid obj) { int o = __offset(16); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
- public IdUid lastRelationId() { return lastRelationId(new IdUid()); }
- public IdUid lastRelationId(IdUid obj) { int o = __offset(18); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.ModelEntity.Vector entitiesVector() { return entitiesVector(new io.objectbox.model.ModelEntity.Vector()); }
+ public io.objectbox.model.ModelEntity.Vector entitiesVector(io.objectbox.model.ModelEntity.Vector obj) { int o = __offset(10); return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; }
+ public io.objectbox.model.IdUid lastEntityId() { return lastEntityId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid lastEntityId(io.objectbox.model.IdUid obj) { int o = __offset(12); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid lastIndexId() { return lastIndexId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid lastIndexId(io.objectbox.model.IdUid obj) { int o = __offset(14); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid lastSequenceId() { return lastSequenceId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid lastSequenceId(io.objectbox.model.IdUid obj) { int o = __offset(16); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid lastRelationId() { return lastRelationId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid lastRelationId(io.objectbox.model.IdUid obj) { int o = __offset(18); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
- public static void startModel(FlatBufferBuilder builder) { builder.startObject(8); }
+ public static void startModel(FlatBufferBuilder builder) { builder.startTable(8); }
public static void addModelVersion(FlatBufferBuilder builder, long modelVersion) { builder.addInt(0, (int)modelVersion, (int)0L); }
public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(1, nameOffset, 0); }
public static void addVersion(FlatBufferBuilder builder, long version) { builder.addLong(2, version, 0L); }
@@ -73,9 +77,17 @@ public final class Model extends Table {
public static void addLastSequenceId(FlatBufferBuilder builder, int lastSequenceIdOffset) { builder.addStruct(6, lastSequenceIdOffset, 0); }
public static void addLastRelationId(FlatBufferBuilder builder, int lastRelationIdOffset) { builder.addStruct(7, lastRelationIdOffset, 0); }
public static int endModel(FlatBufferBuilder builder) {
- int o = builder.endObject();
+ int o = builder.endTable();
return o;
}
public static void finishModelBuffer(FlatBufferBuilder builder, int offset) { builder.finish(offset); }
+ public static void finishSizePrefixedModelBuffer(FlatBufferBuilder builder, int offset) { builder.finishSizePrefixed(offset); }
+
+ public static final class Vector extends BaseVector {
+ public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; }
+
+ public Model get(int j) { return get(new Model(), j); }
+ public Model get(Model obj, int j) { return obj.__assign(__indirect(__element(j), bb), bb); }
+ }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/ModelEntity.java b/objectbox-java/src/main/java/io/objectbox/model/ModelEntity.java
index 466c5b8f..b2e4e2d1 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/ModelEntity.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/ModelEntity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,23 +25,29 @@
@SuppressWarnings("unused")
public final class ModelEntity extends Table {
+ public static void ValidateVersion() { Constants.FLATBUFFERS_1_12_0(); }
public static ModelEntity getRootAsModelEntity(ByteBuffer _bb) { return getRootAsModelEntity(_bb, new ModelEntity()); }
public static ModelEntity getRootAsModelEntity(ByteBuffer _bb, ModelEntity obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
- public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
+ public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }
public ModelEntity __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
- public IdUid id() { return id(new IdUid()); }
- public IdUid id(IdUid obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid id() { return id(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid id(io.objectbox.model.IdUid obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
public String name() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(6, 1); }
- public ModelProperty properties(int j) { return properties(new ModelProperty(), j); }
- public ModelProperty properties(ModelProperty obj, int j) { int o = __offset(8); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
+ public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); }
+ public io.objectbox.model.ModelProperty properties(int j) { return properties(new io.objectbox.model.ModelProperty(), j); }
+ public io.objectbox.model.ModelProperty properties(io.objectbox.model.ModelProperty obj, int j) { int o = __offset(8); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
public int propertiesLength() { int o = __offset(8); return o != 0 ? __vector_len(o) : 0; }
- public IdUid lastPropertyId() { return lastPropertyId(new IdUid()); }
- public IdUid lastPropertyId(IdUid obj) { int o = __offset(10); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
- public ModelRelation relations(int j) { return relations(new ModelRelation(), j); }
- public ModelRelation relations(ModelRelation obj, int j) { int o = __offset(12); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
+ public io.objectbox.model.ModelProperty.Vector propertiesVector() { return propertiesVector(new io.objectbox.model.ModelProperty.Vector()); }
+ public io.objectbox.model.ModelProperty.Vector propertiesVector(io.objectbox.model.ModelProperty.Vector obj) { int o = __offset(8); return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; }
+ public io.objectbox.model.IdUid lastPropertyId() { return lastPropertyId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid lastPropertyId(io.objectbox.model.IdUid obj) { int o = __offset(10); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.ModelRelation relations(int j) { return relations(new io.objectbox.model.ModelRelation(), j); }
+ public io.objectbox.model.ModelRelation relations(io.objectbox.model.ModelRelation obj, int j) { int o = __offset(12); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
public int relationsLength() { int o = __offset(12); return o != 0 ? __vector_len(o) : 0; }
+ public io.objectbox.model.ModelRelation.Vector relationsVector() { return relationsVector(new io.objectbox.model.ModelRelation.Vector()); }
+ public io.objectbox.model.ModelRelation.Vector relationsVector(io.objectbox.model.ModelRelation.Vector obj) { int o = __offset(12); return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; }
/**
* Can be language specific, e.g. if no-args constructor should be used
*/
@@ -51,8 +57,9 @@ public final class ModelEntity extends Table {
*/
public String nameSecondary() { int o = __offset(16); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameSecondaryAsByteBuffer() { return __vector_as_bytebuffer(16, 1); }
+ public ByteBuffer nameSecondaryInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 16, 1); }
- public static void startModelEntity(FlatBufferBuilder builder) { builder.startObject(7); }
+ public static void startModelEntity(FlatBufferBuilder builder) { builder.startTable(7); }
public static void addId(FlatBufferBuilder builder, int idOffset) { builder.addStruct(0, idOffset, 0); }
public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(1, nameOffset, 0); }
public static void addProperties(FlatBufferBuilder builder, int propertiesOffset) { builder.addOffset(2, propertiesOffset, 0); }
@@ -65,8 +72,15 @@ public final class ModelEntity extends Table {
public static void addFlags(FlatBufferBuilder builder, long flags) { builder.addInt(5, (int)flags, (int)0L); }
public static void addNameSecondary(FlatBufferBuilder builder, int nameSecondaryOffset) { builder.addOffset(6, nameSecondaryOffset, 0); }
public static int endModelEntity(FlatBufferBuilder builder) {
- int o = builder.endObject();
+ int o = builder.endTable();
return o;
}
+
+ public static final class Vector extends BaseVector {
+ public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; }
+
+ public ModelEntity get(int j) { return get(new ModelEntity(), j); }
+ public ModelEntity get(ModelEntity obj, int j) { return obj.__assign(__indirect(__element(j), bb), bb); }
+ }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/ModelProperty.java b/objectbox-java/src/main/java/io/objectbox/model/ModelProperty.java
index b2782ff9..64c5edb4 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/ModelProperty.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/ModelProperty.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,39 +25,48 @@
@SuppressWarnings("unused")
public final class ModelProperty extends Table {
+ public static void ValidateVersion() { Constants.FLATBUFFERS_1_12_0(); }
public static ModelProperty getRootAsModelProperty(ByteBuffer _bb) { return getRootAsModelProperty(_bb, new ModelProperty()); }
public static ModelProperty getRootAsModelProperty(ByteBuffer _bb, ModelProperty obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
- public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
+ public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }
public ModelProperty __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
- public IdUid id() { return id(new IdUid()); }
- public IdUid id(IdUid obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid id() { return id(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid id(io.objectbox.model.IdUid obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
public String name() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(6, 1); }
+ public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); }
public int type() { int o = __offset(8); return o != 0 ? bb.getShort(o + bb_pos) & 0xFFFF : 0; }
/**
* bit flags: e.g. indexed, not-nullable
*/
public long flags() { int o = __offset(10); return o != 0 ? (long)bb.getInt(o + bb_pos) & 0xFFFFFFFFL : 0L; }
- public IdUid indexId() { return indexId(new IdUid()); }
- public IdUid indexId(IdUid obj) { int o = __offset(12); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid indexId() { return indexId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid indexId(io.objectbox.model.IdUid obj) { int o = __offset(12); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
/**
* For relations only: name of the target entity
*/
public String targetEntity() { int o = __offset(14); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer targetEntityAsByteBuffer() { return __vector_as_bytebuffer(14, 1); }
+ public ByteBuffer targetEntityInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 14, 1); }
/**
* E.g. for virtual to-one target ID properties, this references the ToOne object
*/
public String virtualTarget() { int o = __offset(16); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer virtualTargetAsByteBuffer() { return __vector_as_bytebuffer(16, 1); }
+ public ByteBuffer virtualTargetInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 16, 1); }
/**
* Secondary name ignored by core; e.g. may reference a binding specific name (e.g. Java property)
*/
public String nameSecondary() { int o = __offset(18); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameSecondaryAsByteBuffer() { return __vector_as_bytebuffer(18, 1); }
+ public ByteBuffer nameSecondaryInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 18, 1); }
+ /**
+ * For value-based indexes, this defines the maximum length of the value stored for indexing
+ */
+ public long maxIndexValueLength() { int o = __offset(20); return o != 0 ? (long)bb.getInt(o + bb_pos) & 0xFFFFFFFFL : 0L; }
- public static void startModelProperty(FlatBufferBuilder builder) { builder.startObject(8); }
+ public static void startModelProperty(FlatBufferBuilder builder) { builder.startTable(9); }
public static void addId(FlatBufferBuilder builder, int idOffset) { builder.addStruct(0, idOffset, 0); }
public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(1, nameOffset, 0); }
public static void addType(FlatBufferBuilder builder, int type) { builder.addShort(2, (short)type, (short)0); }
@@ -66,9 +75,17 @@ public final class ModelProperty extends Table {
public static void addTargetEntity(FlatBufferBuilder builder, int targetEntityOffset) { builder.addOffset(5, targetEntityOffset, 0); }
public static void addVirtualTarget(FlatBufferBuilder builder, int virtualTargetOffset) { builder.addOffset(6, virtualTargetOffset, 0); }
public static void addNameSecondary(FlatBufferBuilder builder, int nameSecondaryOffset) { builder.addOffset(7, nameSecondaryOffset, 0); }
+ public static void addMaxIndexValueLength(FlatBufferBuilder builder, long maxIndexValueLength) { builder.addInt(8, (int)maxIndexValueLength, (int)0L); }
public static int endModelProperty(FlatBufferBuilder builder) {
- int o = builder.endObject();
+ int o = builder.endTable();
return o;
}
+
+ public static final class Vector extends BaseVector {
+ public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; }
+
+ public ModelProperty get(int j) { return get(new ModelProperty(), j); }
+ public ModelProperty get(ModelProperty obj, int j) { return obj.__assign(__indirect(__element(j), bb), bb); }
+ }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/ModelRelation.java b/objectbox-java/src/main/java/io/objectbox/model/ModelRelation.java
index 365412b9..3c03a82a 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/ModelRelation.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/ModelRelation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,25 +25,34 @@
@SuppressWarnings("unused")
public final class ModelRelation extends Table {
+ public static void ValidateVersion() { Constants.FLATBUFFERS_1_12_0(); }
public static ModelRelation getRootAsModelRelation(ByteBuffer _bb) { return getRootAsModelRelation(_bb, new ModelRelation()); }
public static ModelRelation getRootAsModelRelation(ByteBuffer _bb, ModelRelation obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
- public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
+ public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }
public ModelRelation __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
- public IdUid id() { return id(new IdUid()); }
- public IdUid id(IdUid obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public io.objectbox.model.IdUid id() { return id(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid id(io.objectbox.model.IdUid obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
public String name() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(6, 1); }
- public IdUid targetEntityId() { return targetEntityId(new IdUid()); }
- public IdUid targetEntityId(IdUid obj) { int o = __offset(8); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
+ public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); }
+ public io.objectbox.model.IdUid targetEntityId() { return targetEntityId(new io.objectbox.model.IdUid()); }
+ public io.objectbox.model.IdUid targetEntityId(io.objectbox.model.IdUid obj) { int o = __offset(8); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
- public static void startModelRelation(FlatBufferBuilder builder) { builder.startObject(3); }
+ public static void startModelRelation(FlatBufferBuilder builder) { builder.startTable(3); }
public static void addId(FlatBufferBuilder builder, int idOffset) { builder.addStruct(0, idOffset, 0); }
public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(1, nameOffset, 0); }
public static void addTargetEntityId(FlatBufferBuilder builder, int targetEntityIdOffset) { builder.addStruct(2, targetEntityIdOffset, 0); }
public static int endModelRelation(FlatBufferBuilder builder) {
- int o = builder.endObject();
+ int o = builder.endTable();
return o;
}
+
+ public static final class Vector extends BaseVector {
+ public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; }
+
+ public ModelRelation get(int j) { return get(new ModelRelation(), j); }
+ public ModelRelation get(ModelRelation obj, int j) { return obj.__assign(__indirect(__element(j), bb), bb); }
+ }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/PropertyFlags.java b/objectbox-java/src/main/java/io/objectbox/model/PropertyFlags.java
index b18ff2da..ab93b578 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/PropertyFlags.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/PropertyFlags.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,14 @@
package io.objectbox.model;
/**
- * Not really an enum, but binary flags to use across languages
+ * Bit-flags defining the behavior of properties.
+ * Note: Numbers indicate the bit position
*/
public final class PropertyFlags {
private PropertyFlags() { }
/**
- * One long property on an entity must be the ID
+ * 64 bit long property (internally unsigned) representing the ID of the entity.
+ * May be combined with: NON_PRIMITIVE_TYPE, ID_MONOTONIC_SEQUENCE, ID_SELF_ASSIGNABLE.
*/
public static final int ID = 1;
/**
@@ -41,7 +43,7 @@ private PropertyFlags() { }
*/
public static final int RESERVED = 16;
/**
- * Unused yet: Unique index
+ * Unique index
*/
public static final int UNIQUE = 32;
/**
@@ -57,7 +59,8 @@ private PropertyFlags() { }
*/
public static final int INDEX_PARTIAL_SKIP_NULL = 256;
/**
- * Unused yet, used by References for 1) back-references and 2) to clear references to deleted objects (required for ID reuse)
+ * Unused yet in user land.
+ * Used internally by relations for 1) backlinks and 2) to clear references to deleted objects (required for ID reuse).
*/
public static final int INDEX_PARTIAL_SKIP_ZERO = 512;
/**
@@ -65,14 +68,30 @@ private PropertyFlags() { }
*/
public static final int VIRTUAL = 1024;
/**
- * Index uses a 32 bit hash instead of the value
- * (32 bits is shorter on disk, runs well on 32 bit systems, and should be OK even with a few collisions)
+ * Index uses a 32 bit hash instead of the value. 32 bit is the default hash size because:
+ * they take less disk space, run well on 32 bit systems, and also run quite well on 64 bit systems
+ * (especially for small to medium sized values).
+ * and should be OK even with a few collisions.
*/
public static final int INDEX_HASH = 2048;
/**
- * Index uses a 64 bit hash instead of the value
- * (recommended mostly for 64 bit machines with values longer >200 bytes; small values are faster with a 32 bit hash)
+ * Index uses a 64 bit hash instead of the value.
+ * Recommended mostly for 64 bit machines with values longer than 200 bytes;
+ * small values are faster with a 32 bit hash even on 64 bit machines.
*/
public static final int INDEX_HASH64 = 4096;
+ /**
+ * Unused yet: While our default are signed ints, queries and indexes need do know signing info.
+ * Note: Don't combine with ID (IDs are always unsigned internally).
+ */
+ public static final int UNSIGNED = 8192;
+ /**
+ * By defining an ID companion property, the entity type uses a special ID encoding scheme involving this property
+ * in addition to the ID.
+ *
+ * For Time Series IDs, a companion property of type Date or DateNano represents the exact timestamp.
+ * (Future idea: string hash IDs, with a String companion property to store the full string ID).
+ */
+ public static final int ID_COMPANION = 16384;
}
diff --git a/objectbox-java/src/main/java/io/objectbox/model/PropertyType.java b/objectbox-java/src/main/java/io/objectbox/model/PropertyType.java
index 2b08081b..1b1ffc1c 100644
--- a/objectbox-java/src/main/java/io/objectbox/model/PropertyType.java
+++ b/objectbox-java/src/main/java/io/objectbox/model/PropertyType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@
package io.objectbox.model;
+/**
+ * Basic type of a property
+ */
public final class PropertyType {
private PropertyType() { }
/**
@@ -34,14 +37,17 @@ private PropertyType() { }
public static final short Double = 8;
public static final short String = 9;
/**
- * Internally stored as a 64 bit long(?)
+ * Date/time stored as a 64 bit long representing milliseconds since 1970-01-01 (unix epoch)
*/
public static final short Date = 10;
/**
* Relation to another entity
*/
public static final short Relation = 11;
- public static final short Reserved1 = 12;
+ /**
+ * High precision date/time stored as a 64 bit long representing nanoseconds since 1970-01-01 (unix epoch)
+ */
+ public static final short DateNano = 12;
public static final short Reserved2 = 13;
public static final short Reserved3 = 14;
public static final short Reserved4 = 15;
@@ -61,8 +67,10 @@ private PropertyType() { }
public static final short DoubleVector = 29;
public static final short StringVector = 30;
public static final short DateVector = 31;
+ public static final short DateNanoVector = 32;
- public static final String[] names = { "Unknown", "Bool", "Byte", "Short", "Char", "Int", "Long", "Float", "Double", "String", "Date", "Relation", "Reserved1", "Reserved2", "Reserved3", "Reserved4", "Reserved5", "Reserved6", "Reserved7", "Reserved8", "Reserved9", "Reserved10", "BoolVector", "ByteVector", "ShortVector", "CharVector", "IntVector", "LongVector", "FloatVector", "DoubleVector", "StringVector", "DateVector", };
+ // Private to protect contents from getting modified.
+ private static final String[] names = { "Unknown", "Bool", "Byte", "Short", "Char", "Int", "Long", "Float", "Double", "String", "Date", "Relation", "DateNano", "Reserved2", "Reserved3", "Reserved4", "Reserved5", "Reserved6", "Reserved7", "Reserved8", "Reserved9", "Reserved10", "BoolVector", "ByteVector", "ShortVector", "CharVector", "IntVector", "LongVector", "FloatVector", "DoubleVector", "StringVector", "DateVector", "DateNanoVector", };
public static String name(int e) { return names[e]; }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/package-info.java b/objectbox-java/src/main/java/io/objectbox/package-info.java
index 875735ba..4e084547 100644
--- a/objectbox-java/src/main/java/io/objectbox/package-info.java
+++ b/objectbox-java/src/main/java/io/objectbox/package-info.java
@@ -14,6 +14,20 @@
* limitations under the License.
*/
+/**
+ * ObjectBox is an an easy to use, object-oriented lightweight database and a full alternative to SQLite.
+ *
+ * The following core classes are the essential interface to ObjectBox:
+ *
+ *
MyObjectBox: Generated by the Gradle plugin, supplies a {@link io.objectbox.BoxStoreBuilder}
+ * to build a BoxStore for your app.
+ *
{@link io.objectbox.BoxStore}: The database interface, allows to manage Boxes.
+ *
{@link io.objectbox.Box}: Persists and queries for entities, there is one for each entity.
+ *
+ *
+ * For more details look at the documentation of individual classes and
+ * docs.objectbox.io.
+ */
@ParametersAreNonnullByDefault
package io.objectbox;
diff --git a/objectbox-java/src/main/java/io/objectbox/query/EagerRelation.java b/objectbox-java/src/main/java/io/objectbox/query/EagerRelation.java
index 2bffb395..63ad47ba 100644
--- a/objectbox-java/src/main/java/io/objectbox/query/EagerRelation.java
+++ b/objectbox-java/src/main/java/io/objectbox/query/EagerRelation.java
@@ -18,11 +18,11 @@
import io.objectbox.relation.RelationInfo;
-class EagerRelation {
+class EagerRelation {
public final int limit;
- public final RelationInfo relationInfo;
+ public final RelationInfo relationInfo;
- EagerRelation(int limit, RelationInfo relationInfo) {
+ EagerRelation(int limit, RelationInfo relationInfo) {
this.limit = limit;
this.relationInfo = relationInfo;
}
diff --git a/objectbox-java/src/main/java/io/objectbox/query/LazyList.java b/objectbox-java/src/main/java/io/objectbox/query/LazyList.java
index 452e1d2f..c1ba765e 100644
--- a/objectbox-java/src/main/java/io/objectbox/query/LazyList.java
+++ b/objectbox-java/src/main/java/io/objectbox/query/LazyList.java
@@ -136,12 +136,10 @@ public void loadRemaining() {
if (loadedCount != size) {
checkCached();
// use single reader only for efficiency
- box.getStore().runInReadTx(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < size; i++) {
- get(i);
- }
+ box.getStore().runInReadTx(() -> {
+ for (int i = 0; i < size; i++) {
+ //noinspection ResultOfMethodCallIgnored
+ get(i);
}
});
}
diff --git a/objectbox-java/src/main/java/io/objectbox/query/OrderFlags.java b/objectbox-java/src/main/java/io/objectbox/query/OrderFlags.java
index 946cbd84..1d8fc38d 100644
--- a/objectbox-java/src/main/java/io/objectbox/query/OrderFlags.java
+++ b/objectbox-java/src/main/java/io/objectbox/query/OrderFlags.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,8 @@ private OrderFlags() { }
*/
public static final int NULLS_ZERO = 16;
- public static final String[] names = { "DESCENDING", "CASE_SENSITIVE", "", "UNSIGNED", "", "", "", "NULLS_LAST", "", "", "", "", "", "", "", "NULLS_ZERO", };
+ // Private to protect contents from getting modified.
+ private static final String[] names = { "DESCENDING", "CASE_SENSITIVE", "", "UNSIGNED", "", "", "", "NULLS_LAST", "", "", "", "", "", "", "", "NULLS_ZERO", };
public static String name(int e) { return names[e - DESCENDING]; }
}
diff --git a/objectbox-java/src/main/java/io/objectbox/query/PropertyQuery.java b/objectbox-java/src/main/java/io/objectbox/query/PropertyQuery.java
index 528b45ea..9ddc5100 100644
--- a/objectbox-java/src/main/java/io/objectbox/query/PropertyQuery.java
+++ b/objectbox-java/src/main/java/io/objectbox/query/PropertyQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
+ * Copyright 2017-2020 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,6 @@
package io.objectbox.query;
-import java.util.concurrent.Callable;
-
import io.objectbox.Property;
/**
@@ -28,9 +26,9 @@
*/
@SuppressWarnings("WeakerAccess") // WeakerAccess: allow inner class access without accessor
public class PropertyQuery {
- final Query query;
+ final Query> query;
final long queryHandle;
- final Property property;
+ final Property> property;
final int propertyId;
boolean distinct;
@@ -43,13 +41,61 @@ public class PropertyQuery {
String nullValueString;
long nullValueLong;
- PropertyQuery(Query query, Property property) {
+ PropertyQuery(Query> query, Property> property) {
this.query = query;
queryHandle = query.handle;
this.property = property;
propertyId = property.id;
}
+ native String[] nativeFindStrings(long handle, long cursorHandle, int propertyId, boolean distinct,
+ boolean distinctNoCase, boolean enableNull, String nullValue);
+
+ native long[] nativeFindLongs(long handle, long cursorHandle, int propertyId, boolean distinct, boolean enableNull,
+ long nullValue);
+
+ native int[] nativeFindInts(long handle, long cursorHandle, int propertyId, boolean distinct, boolean enableNull,
+ int nullValue);
+
+ native short[] nativeFindShorts(long handle, long cursorHandle, int propertyId, boolean distinct,
+ boolean enableNull, short nullValue);
+
+ native char[] nativeFindChars(long handle, long cursorHandle, int propertyId, boolean distinct, boolean enableNull,
+ char nullValue);
+
+ native byte[] nativeFindBytes(long handle, long cursorHandle, int propertyId, boolean distinct, boolean enableNull,
+ byte nullValue);
+
+ native float[] nativeFindFloats(long handle, long cursorHandle, int propertyId, boolean distinct,
+ boolean enableNull, float nullValue);
+
+ native double[] nativeFindDoubles(long handle, long cursorHandle, int propertyId, boolean distinct,
+ boolean enableNull, double nullValue);
+
+ native Object nativeFindNumber(long handle, long cursorHandle, int propertyId, boolean unique, boolean distinct,
+ boolean enableNull, long nullValue, float nullValueFloat, double nullValueDouble);
+
+ native String nativeFindString(long handle, long cursorHandle, int propertyId, boolean unique, boolean distinct,
+ boolean distinctCase, boolean enableNull, String nullValue);
+
+ native long nativeSum(long handle, long cursorHandle, int propertyId);
+
+ native double nativeSumDouble(long handle, long cursorHandle, int propertyId);
+
+ native long nativeMax(long handle, long cursorHandle, int propertyId);
+
+ native double nativeMaxDouble(long handle, long cursorHandle, int propertyId);
+
+ native long nativeMin(long handle, long cursorHandle, int propertyId);
+
+ native double nativeMinDouble(long handle, long cursorHandle, int propertyId);
+
+ native double nativeAvg(long handle, long cursorHandle, int propertyId);
+
+ native long nativeAvgLong(long handle, long cursorHandle, int propertyId);
+
+ native long nativeCount(long handle, long cursorHandle, int propertyId, boolean distinct);
+
/** Clears all values (e.g. distinct and null value). */
public PropertyQuery reset() {
distinct = false;
@@ -107,6 +153,7 @@ public PropertyQuery unique() {
* E.g. -1 for ins/longs or "NULL" for strings.
*/
public PropertyQuery nullValue(Object nullValue) {
+ //noinspection ConstantConditions Annotation can not enforce non-null.
if (nullValue == null) {
throw new IllegalArgumentException("Null values are not allowed");
}
@@ -133,19 +180,16 @@ public PropertyQuery nullValue(Object nullValue) {
*
* Note: results are not guaranteed to be in any particular order.
*