Skip to content

Commit d1a8912

Browse files
Merge branch '82-null-db-exception-listener' into 'dev'
BoxStore: do not warn on (Java)/allow null (Kotlin) DbExceptionListener. See merge request objectbox/objectbox-java!73
2 parents 39671a1 + b9b2cc1 commit d1a8912

File tree

2 files changed

+128
-6
lines changed

2 files changed

+128
-6
lines changed

objectbox-java/src/main/java/io/objectbox/BoxStore.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package io.objectbox;
1818

19-
import io.objectbox.annotation.apihint.Beta;
2019
import org.greenrobot.essentials.collections.LongHashMap;
2120

2221
import java.io.Closeable;
@@ -40,6 +39,7 @@
4039
import javax.annotation.Nullable;
4140
import javax.annotation.concurrent.ThreadSafe;
4241

42+
import io.objectbox.annotation.apihint.Beta;
4343
import io.objectbox.annotation.apihint.Experimental;
4444
import io.objectbox.annotation.apihint.Internal;
4545
import io.objectbox.converter.PropertyConverter;
@@ -165,7 +165,7 @@ static native void nativeRegisterCustomType(long store, int entityId, int proper
165165

166166
static native int nativeCleanStaleReadTransactions(long store);
167167

168-
static native void nativeSetDbExceptionListener(long store, DbExceptionListener dbExceptionListener);
168+
static native void nativeSetDbExceptionListener(long store, @Nullable DbExceptionListener dbExceptionListener);
169169

170170
static native void nativeSetDebugFlags(long store, int debugFlags);
171171

@@ -1027,10 +1027,12 @@ private void verifyObjectBrowserNotRunning() {
10271027
}
10281028

10291029
/**
1030-
* The given listener will be called when an exception is thrown.
1031-
* This for example allows a central error handling, e.g. a special logging for DB related exceptions.
1030+
* Sets a listener that will be called when an exception is thrown. Replaces a previously set listener.
1031+
* Set to {@code null} to remove the listener.
1032+
* <p>
1033+
* This for example allows central error handling or special logging for database-related exceptions.
10321034
*/
1033-
public void setDbExceptionListener(DbExceptionListener dbExceptionListener) {
1035+
public void setDbExceptionListener(@Nullable DbExceptionListener dbExceptionListener) {
10341036
nativeSetDbExceptionListener(handle, dbExceptionListener);
10351037
}
10361038

tests/objectbox-java-test/src/test/java/io/objectbox/exception/ExceptionTest.java

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,136 @@
1616

1717
package io.objectbox.exception;
1818

19-
import io.objectbox.AbstractObjectBoxTest;
2019
import org.junit.Test;
2120

21+
import java.lang.ref.WeakReference;
2222
import java.util.ArrayList;
2323
import java.util.List;
24+
import java.util.concurrent.atomic.AtomicBoolean;
25+
26+
import io.objectbox.AbstractObjectBoxTest;
27+
2428

2529
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertFalse;
31+
import static org.junit.Assert.assertThrows;
32+
import static org.junit.Assert.assertTrue;
2633

34+
/**
35+
* Tests related to {@link DbExceptionListener}.
36+
*
37+
* Note: this test has an equivalent in test-java-android integration tests.
38+
*/
2739
public class ExceptionTest extends AbstractObjectBoxTest {
2840

41+
@Test
42+
public void exceptionListener_null_works() {
43+
store.setDbExceptionListener(null);
44+
}
45+
46+
@Test
47+
public void exceptionListener_closedStore_works() {
48+
store.close();
49+
store.setDbExceptionListener(e -> System.out.println("This is never called"));
50+
}
51+
52+
private static boolean weakRefListenerCalled = false;
53+
54+
@Test
55+
public void exceptionListener_noLocalRef_works() throws InterruptedException {
56+
// Note: do not use lambda, it would keep a reference to this class
57+
// and prevent garbage collection of the listener.
58+
//noinspection Convert2Lambda
59+
DbExceptionListener listenerNoRef = new DbExceptionListener() {
60+
@Override
61+
public void onDbException(Exception e) {
62+
System.out.println("Listener without strong reference is called");
63+
weakRefListenerCalled = true;
64+
}
65+
};
66+
WeakReference<DbExceptionListener> weakReference = new WeakReference<>(listenerNoRef);
67+
68+
// Set and clear local reference.
69+
store.setDbExceptionListener(listenerNoRef);
70+
//noinspection UnusedAssignment
71+
listenerNoRef = null;
72+
73+
// Try and fail to release weak reference.
74+
int triesClearWeakRef = 5;
75+
while (weakReference.get() != null) {
76+
if (--triesClearWeakRef == 0) break;
77+
System.out.println("Suggesting GC");
78+
System.gc();
79+
//noinspection BusyWait
80+
Thread.sleep(300);
81+
}
82+
assertEquals("Failed to keep weak reference to listener", 0, triesClearWeakRef);
83+
84+
// Throw, listener should be called.
85+
weakRefListenerCalled = false;
86+
assertThrows(
87+
DbException.class,
88+
() -> DbExceptionListenerJni.nativeThrowException(store.getNativeStore(), 0)
89+
);
90+
assertTrue(weakRefListenerCalled);
91+
92+
// Remove reference from native side.
93+
store.setDbExceptionListener(null);
94+
95+
// Try and succeed to release weak reference.
96+
triesClearWeakRef = 5;
97+
while (weakReference.get() != null) {
98+
if (--triesClearWeakRef == 0) break;
99+
System.out.println("Suggesting GC");
100+
System.gc();
101+
//noinspection BusyWait
102+
Thread.sleep(300);
103+
}
104+
assertTrue("Failed to release weak reference to listener", triesClearWeakRef > 0);
105+
106+
// Throw, listener should not be called.
107+
weakRefListenerCalled = false;
108+
assertThrows(
109+
DbException.class,
110+
() -> DbExceptionListenerJni.nativeThrowException(store.getNativeStore(), 0)
111+
);
112+
assertFalse(weakRefListenerCalled);
113+
}
114+
115+
@Test
116+
public void exceptionListener_removing_works() {
117+
AtomicBoolean replacedListenerCalled = new AtomicBoolean(false);
118+
DbExceptionListener listenerRemoved = e -> replacedListenerCalled.set(true);
119+
120+
store.setDbExceptionListener(listenerRemoved);
121+
store.setDbExceptionListener(null);
122+
123+
assertThrows(
124+
DbException.class,
125+
() -> DbExceptionListenerJni.nativeThrowException(store.getNativeStore(), 0)
126+
);
127+
assertFalse("Should not have called removed DbExceptionListener.", replacedListenerCalled.get());
128+
}
129+
130+
@Test
131+
public void exceptionListener_replacing_works() {
132+
AtomicBoolean replacedListenerCalled = new AtomicBoolean(false);
133+
DbExceptionListener listenerReplaced = e -> replacedListenerCalled.set(true);
134+
135+
AtomicBoolean newListenerCalled = new AtomicBoolean(false);
136+
DbExceptionListener listenerNew = e -> newListenerCalled.set(true);
137+
138+
store.setDbExceptionListener(listenerReplaced);
139+
store.setDbExceptionListener(listenerNew);
140+
141+
assertThrows(
142+
DbException.class,
143+
() -> DbExceptionListenerJni.nativeThrowException(store.getNativeStore(), 0)
144+
);
145+
assertFalse("Should not have called replaced DbExceptionListener.", replacedListenerCalled.get());
146+
assertTrue("Failed to call new DbExceptionListener.", newListenerCalled.get());
147+
}
148+
29149
@Test
30150
public void testThrowExceptions() {
31151
final List<Exception> exs = new ArrayList<>();

0 commit comments

Comments
 (0)