1
1
/*
2
- * Copyright 2017 ObjectBox Ltd. All rights reserved.
2
+ * Copyright 2017-2024 ObjectBox Ltd. All rights reserved.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
36
36
import io .objectbox .Cursor ;
37
37
import io .objectbox .InternalAccess ;
38
38
import io .objectbox .annotation .Backlink ;
39
+ import io .objectbox .annotation .Entity ;
39
40
import io .objectbox .annotation .apihint .Beta ;
40
41
import io .objectbox .annotation .apihint .Experimental ;
41
42
import io .objectbox .annotation .apihint .Internal ;
51
52
import static java .lang .Boolean .TRUE ;
52
53
53
54
/**
54
- * A lazily loaded {@link List} of target objects representing a to-many relation, a unidirectional link from a "source"
55
- * entity to multiple objects of a "target" entity.
55
+ * A to-many relation of an entity that references multiple objects of a {@link TARGET} entity.
56
56
* <p>
57
- * It tracks changes (adds and removes) that can be later applied (persisted) to the database. This happens either when
58
- * the object that contains this relation is put or using {@link #applyChangesToDb()}. For some important details about
59
- * applying changes, see the notes about relations of {@link Box#put(Object)}.
57
+ * Example:
58
+ * <pre>{@code
59
+ * // Java
60
+ * @Entity
61
+ * public class Student{
62
+ * private ToMany<Teacher> teachers;
63
+ * }
64
+ *
65
+ * // Kotlin
66
+ * @Entity
67
+ * data class Student() {
68
+ * lateinit var teachers: ToMany<Teacher>
69
+ * }
70
+ * }</pre>
60
71
* <p>
61
- * The objects are loaded lazily on first access of this list, and then cached. The database query runs on the calling
62
- * thread, so avoid accessing this from a UI or main thread. Subsequent calls to any method, like {@link #size()}, do
63
- * not query the database, even if the relation was changed elsewhere. To get the latest data {@link Box#get} the source
64
- * object again or use {@link #reset()} before accessing the list again.
72
+ * Implements the {@link List} interface and uses lazy initialization. The target objects are only read from the
73
+ * database when the list is first accessed.
65
74
* <p>
75
+ * The required database query runs on the calling thread, so avoid accessing ToMany from a UI or main thread. To get the
76
+ * latest data {@link Box#get} the object with the ToMany again or use {@link #reset()} before accessing the list again.
66
77
* It is possible to preload the list when running a query using {@link QueryBuilder#eager}.
67
78
* <p>
79
+ * Tracks when target objects are added and removed. Common usage:
80
+ * <ul>
81
+ * <li>{@link #add(Object)} to add target objects to the relation.
82
+ * <li>{@link #remove(Object)} to remove target objects from the relation.
83
+ * <li>{@link #remove(int)} to remove target objects at a specific index.
84
+ * </ul>
85
+ * <p>
86
+ * To apply (persist) the changes to the database, call {@link #applyChangesToDb()} or put the object with the ToMany.
87
+ * For important details, see the notes about relations of {@link Box#put(Object)}.
88
+ * <p>
89
+ * <pre>{@code
90
+ * // Example 1: add target objects to a relation
91
+ * student.getTeachers().add(teacher1);
92
+ * student.getTeachers().add(teacher2);
93
+ * store.boxFor(Student.class).put(student);
94
+ *
95
+ * // Example 2: remove a target object from the relation
96
+ * student.getTeachers().remove(index);
97
+ * student.getTeachers().applyChangesToDb();
98
+ * // or store.boxFor(Student.class).put(student);
99
+ * }</pre>
100
+ * <p>
101
+ * In the database, the target objects are referenced by their IDs, which are persisted as part of the relation of the
102
+ * object with the ToMany.
103
+ * <p>
68
104
* ToMany is thread-safe by default (may not be the case if {@link #setListFactory(ListFactory)} is used).
105
+ * <p>
106
+ * To get all objects with a ToMany that reference a target object, see {@link Backlink}.
69
107
*
70
- * @param <TARGET> Object type (entity ).
108
+ * @param <TARGET> target object type ({@link Entity @Entity} class ).
71
109
*/
72
110
public class ToMany <TARGET > implements List <TARGET >, Serializable {
73
111
private static final long serialVersionUID = 2367317778240689006L ;
@@ -155,8 +193,8 @@ private void ensureBoxes() {
155
193
try {
156
194
boxStore = (BoxStore ) boxStoreField .get (entity );
157
195
if (boxStore == null ) {
158
- throw new DbDetachedException ("Cannot resolve relation for detached entities , " +
159
- "call box.attach(entity ) beforehand." );
196
+ throw new DbDetachedException ("Cannot resolve relation for detached objects , " +
197
+ "call box.attach(object ) beforehand." );
160
198
}
161
199
} catch (IllegalAccessException e ) {
162
200
throw new RuntimeException (e );
@@ -226,9 +264,10 @@ private void ensureEntities() {
226
264
}
227
265
228
266
/**
229
- * Adds the given entity to the list and tracks the addition so it can be later applied to the database
230
- * (e.g. via {@link Box#put(Object)} of the entity owning the ToMany, or via {@link #applyChangesToDb()}).
231
- * Note that the given entity will remain unchanged at this point (e.g. to-ones are not updated).
267
+ * Prepares to add the given target object to this relation.
268
+ * <p>
269
+ * To apply changes, call {@link #applyChangesToDb()} or put the object with the ToMany. For important details, see
270
+ * the notes about relations of {@link Box#put(Object)}.
232
271
*/
233
272
@ Override
234
273
public synchronized boolean add (TARGET object ) {
@@ -329,8 +368,9 @@ public boolean containsAll(Collection<?> collection) {
329
368
}
330
369
331
370
/**
332
- * @return An object for the given ID, or null if the object was already removed from its box
333
- * (and was not cached before).
371
+ * Gets the target object at the given index.
372
+ * <p>
373
+ * {@link ToMany} uses lazy initialization, so on first access this will read the target objects from the database.
334
374
*/
335
375
@ Override
336
376
public TARGET get (int location ) {
@@ -381,6 +421,9 @@ public ListIterator<TARGET> listIterator(int location) {
381
421
return entities .listIterator (location );
382
422
}
383
423
424
+ /**
425
+ * Like {@link #remove(Object)}, but using the location of the target object.
426
+ */
384
427
@ Override
385
428
public synchronized TARGET remove (int location ) {
386
429
ensureEntitiesWithTrackingLists ();
@@ -389,6 +432,12 @@ public synchronized TARGET remove(int location) {
389
432
return removed ;
390
433
}
391
434
435
+ /**
436
+ * Prepares to remove the target object from this relation.
437
+ * <p>
438
+ * To apply changes, call {@link #applyChangesToDb()} or put the object with the ToMany. For important details, see
439
+ * the notes about relations of {@link Box#put(Object)}.
440
+ */
392
441
@ SuppressWarnings ("unchecked" ) // Cast to TARGET: If removed, must be of type TARGET.
393
442
@ Override
394
443
public synchronized boolean remove (Object object ) {
@@ -400,7 +449,9 @@ public synchronized boolean remove(Object object) {
400
449
return removed ;
401
450
}
402
451
403
- /** Removes an object by its entity ID. */
452
+ /**
453
+ * Like {@link #remove(Object)}, but using just the ID of the target object.
454
+ */
404
455
public synchronized TARGET removeById (long id ) {
405
456
ensureEntities ();
406
457
int size = entities .size ();
@@ -519,9 +570,9 @@ public int getRemoveCount() {
519
570
}
520
571
521
572
/**
522
- * Sorts the list by the "natural" ObjectBox order for to-many list (by entity ID).
523
- * This will be the order when you get the entities fresh (e.g. initially or after calling {@link #reset()}).
524
- * Note that non persisted entities (ID is zero) will be put to the end as they are still to get an ID.
573
+ * Sorts the list by the "natural" ObjectBox order for to-many list (by object ID).
574
+ * This will be the order when you get the objects fresh (e.g. initially or after calling {@link #reset()}).
575
+ * Note that non persisted objects (ID is zero) will be put to the end as they are still to get an ID.
525
576
*/
526
577
public void sortById () {
527
578
ensureEntities ();
@@ -550,7 +601,7 @@ else if (delta > 0)
550
601
}
551
602
552
603
/**
553
- * Saves changes (added and removed entities ) made to this relation to the database. For some important details, see
604
+ * Saves changes (added and removed objects ) made to this relation to the database. For some important details, see
554
605
* the notes about relations of {@link Box#put(Object)}.
555
606
* <p>
556
607
* Note that this is called already when the object that contains this ToMany is put. However, if only this ToMany
@@ -563,12 +614,12 @@ public void applyChangesToDb() {
563
614
long id = relationInfo .sourceInfo .getIdGetter ().getId (entity );
564
615
if (id == 0 ) {
565
616
throw new IllegalStateException (
566
- "The source entity was not yet persisted (no ID), use box.put() on it instead" );
617
+ "The object with the ToMany was not yet persisted (no ID), use box.put() on it instead" );
567
618
}
568
619
try {
569
620
ensureBoxes ();
570
621
} catch (DbDetachedException e ) {
571
- throw new IllegalStateException ("The source entity was not yet persisted, use box.put() on it instead" );
622
+ throw new IllegalStateException ("The object with the ToMany was not yet persisted, use box.put() on it instead" );
572
623
}
573
624
if (internalCheckApplyToDbRequired ()) {
574
625
// We need a TX because we use two writers and both must use same TX (without: unchecked, SIGSEGV)
@@ -581,10 +632,10 @@ public void applyChangesToDb() {
581
632
}
582
633
583
634
/**
584
- * Returns true if at least one of the entities matches the given filter.
635
+ * Returns true if at least one of the target objects matches the given filter.
585
636
* <p>
586
637
* For use with {@link QueryBuilder#filter(QueryFilter)} inside a {@link QueryFilter} to check
587
- * to-many relation entities .
638
+ * to-many relation objects .
588
639
*/
589
640
@ Beta
590
641
public boolean hasA (QueryFilter <TARGET > filter ) {
@@ -599,10 +650,10 @@ public boolean hasA(QueryFilter<TARGET> filter) {
599
650
}
600
651
601
652
/**
602
- * Returns true if all of the entities match the given filter. Returns false if the list is empty.
653
+ * Returns true if all of the target objects match the given filter. Returns false if the list is empty.
603
654
* <p>
604
655
* For use with {@link QueryBuilder#filter(QueryFilter)} inside a {@link QueryFilter} to check
605
- * to-many relation entities .
656
+ * to-many relation objects .
606
657
*/
607
658
@ Beta
608
659
public boolean hasAll (QueryFilter <TARGET > filter ) {
@@ -619,7 +670,7 @@ public boolean hasAll(QueryFilter<TARGET> filter) {
619
670
return true ;
620
671
}
621
672
622
- /** Gets an object by its entity ID. */
673
+ /** Gets an object by its ID. */
623
674
@ Beta
624
675
public TARGET getById (long id ) {
625
676
ensureEntities ();
@@ -634,7 +685,7 @@ public TARGET getById(long id) {
634
685
return null ;
635
686
}
636
687
637
- /** Gets the index of the object with the given entity ID. */
688
+ /** Gets the index of the object with the given ID. */
638
689
@ Beta
639
690
public int indexOfId (long id ) {
640
691
ensureEntities ();
@@ -653,7 +704,7 @@ public int indexOfId(long id) {
653
704
654
705
/**
655
706
* Returns true if there are pending changes for the DB.
656
- * Changes will be automatically persisted once the owning entity is put, or an explicit call to
707
+ * Changes will be automatically persisted once the object with the ToMany is put, or an explicit call to
657
708
* {@link #applyChangesToDb()} is made.
658
709
*/
659
710
public boolean hasPendingDbChanges () {
@@ -668,7 +719,7 @@ public boolean hasPendingDbChanges() {
668
719
669
720
/**
670
721
* For internal use only; do not use in your app.
671
- * Called after relation source entity is put (so we have its ID).
722
+ * Called after relation source object is put (so we have its ID).
672
723
* Prepares data for {@link #internalApplyToDb(Cursor, Cursor)}
673
724
*/
674
725
@ Internal
@@ -692,7 +743,7 @@ public boolean internalCheckApplyToDbRequired() {
692
743
// Relation based on Backlink
693
744
long entityId = relationInfo .sourceInfo .getIdGetter ().getId (entity );
694
745
if (entityId == 0 ) {
695
- throw new IllegalStateException ("Source entity has no ID (should have been put before)" );
746
+ throw new IllegalStateException ("Object with the ToMany has no ID (should have been put before)" );
696
747
}
697
748
IdGetter <TARGET > idGetter = relationInfo .targetInfo .getIdGetter ();
698
749
Map <TARGET , Boolean > setAdded = this .entitiesAdded ;
@@ -866,7 +917,7 @@ public void internalApplyToDb(Cursor<?> sourceCursor, Cursor<TARGET> targetCurso
866
917
if (isStandaloneRelation ) {
867
918
long entityId = relationInfo .sourceInfo .getIdGetter ().getId (entity );
868
919
if (entityId == 0 ) {
869
- throw new IllegalStateException ("Source entity has no ID (should have been put before)" );
920
+ throw new IllegalStateException ("Object with the ToMany has no ID (should have been put before)" );
870
921
}
871
922
872
923
if (removedStandalone != null ) {
@@ -909,7 +960,7 @@ private void addStandaloneRelations(Cursor<?> cursor, long sourceEntityId, TARGE
909
960
long targetId = targetIdGetter .getId (added [i ]);
910
961
if (targetId == 0 ) {
911
962
// Paranoia
912
- throw new IllegalStateException ("Target entity has no ID (should have been put before)" );
963
+ throw new IllegalStateException ("Target object has no ID (should have been put before)" );
913
964
}
914
965
targetIds [i ] = targetId ;
915
966
}
0 commit comments