11/*
2- * Copyright 2017 ObjectBox Ltd. All rights reserved.
2+ * Copyright 2017-2024 ObjectBox Ltd. All rights reserved.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
3636import io .objectbox .Cursor ;
3737import io .objectbox .InternalAccess ;
3838import io .objectbox .annotation .Backlink ;
39+ import io .objectbox .annotation .Entity ;
3940import io .objectbox .annotation .apihint .Beta ;
4041import io .objectbox .annotation .apihint .Experimental ;
4142import io .objectbox .annotation .apihint .Internal ;
5152import static java .lang .Boolean .TRUE ;
5253
5354/**
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.
5656 * <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>
6071 * <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.
6574 * <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.
6677 * It is possible to preload the list when running a query using {@link QueryBuilder#eager}.
6778 * <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>
68104 * 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}.
69107 *
70- * @param <TARGET> Object type (entity ).
108+ * @param <TARGET> target object type ({@link Entity @Entity} class ).
71109 */
72110public class ToMany <TARGET > implements List <TARGET >, Serializable {
73111 private static final long serialVersionUID = 2367317778240689006L ;
@@ -155,8 +193,8 @@ private void ensureBoxes() {
155193 try {
156194 boxStore = (BoxStore ) boxStoreField .get (entity );
157195 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." );
160198 }
161199 } catch (IllegalAccessException e ) {
162200 throw new RuntimeException (e );
@@ -226,9 +264,10 @@ private void ensureEntities() {
226264 }
227265
228266 /**
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)}.
232271 */
233272 @ Override
234273 public synchronized boolean add (TARGET object ) {
@@ -329,8 +368,9 @@ public boolean containsAll(Collection<?> collection) {
329368 }
330369
331370 /**
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.
334374 */
335375 @ Override
336376 public TARGET get (int location ) {
@@ -381,6 +421,9 @@ public ListIterator<TARGET> listIterator(int location) {
381421 return entities .listIterator (location );
382422 }
383423
424+ /**
425+ * Like {@link #remove(Object)}, but using the location of the target object.
426+ */
384427 @ Override
385428 public synchronized TARGET remove (int location ) {
386429 ensureEntitiesWithTrackingLists ();
@@ -389,6 +432,12 @@ public synchronized TARGET remove(int location) {
389432 return removed ;
390433 }
391434
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+ */
392441 @ SuppressWarnings ("unchecked" ) // Cast to TARGET: If removed, must be of type TARGET.
393442 @ Override
394443 public synchronized boolean remove (Object object ) {
@@ -400,7 +449,9 @@ public synchronized boolean remove(Object object) {
400449 return removed ;
401450 }
402451
403- /** Removes an object by its entity ID. */
452+ /**
453+ * Like {@link #remove(Object)}, but using just the ID of the target object.
454+ */
404455 public synchronized TARGET removeById (long id ) {
405456 ensureEntities ();
406457 int size = entities .size ();
@@ -519,9 +570,9 @@ public int getRemoveCount() {
519570 }
520571
521572 /**
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.
525576 */
526577 public void sortById () {
527578 ensureEntities ();
@@ -550,7 +601,7 @@ else if (delta > 0)
550601 }
551602
552603 /**
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
554605 * the notes about relations of {@link Box#put(Object)}.
555606 * <p>
556607 * 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() {
563614 long id = relationInfo .sourceInfo .getIdGetter ().getId (entity );
564615 if (id == 0 ) {
565616 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" );
567618 }
568619 try {
569620 ensureBoxes ();
570621 } 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" );
572623 }
573624 if (internalCheckApplyToDbRequired ()) {
574625 // 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() {
581632 }
582633
583634 /**
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.
585636 * <p>
586637 * For use with {@link QueryBuilder#filter(QueryFilter)} inside a {@link QueryFilter} to check
587- * to-many relation entities .
638+ * to-many relation objects .
588639 */
589640 @ Beta
590641 public boolean hasA (QueryFilter <TARGET > filter ) {
@@ -599,10 +650,10 @@ public boolean hasA(QueryFilter<TARGET> filter) {
599650 }
600651
601652 /**
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.
603654 * <p>
604655 * For use with {@link QueryBuilder#filter(QueryFilter)} inside a {@link QueryFilter} to check
605- * to-many relation entities .
656+ * to-many relation objects .
606657 */
607658 @ Beta
608659 public boolean hasAll (QueryFilter <TARGET > filter ) {
@@ -619,7 +670,7 @@ public boolean hasAll(QueryFilter<TARGET> filter) {
619670 return true ;
620671 }
621672
622- /** Gets an object by its entity ID. */
673+ /** Gets an object by its ID. */
623674 @ Beta
624675 public TARGET getById (long id ) {
625676 ensureEntities ();
@@ -634,7 +685,7 @@ public TARGET getById(long id) {
634685 return null ;
635686 }
636687
637- /** Gets the index of the object with the given entity ID. */
688+ /** Gets the index of the object with the given ID. */
638689 @ Beta
639690 public int indexOfId (long id ) {
640691 ensureEntities ();
@@ -653,7 +704,7 @@ public int indexOfId(long id) {
653704
654705 /**
655706 * 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
657708 * {@link #applyChangesToDb()} is made.
658709 */
659710 public boolean hasPendingDbChanges () {
@@ -668,7 +719,7 @@ public boolean hasPendingDbChanges() {
668719
669720 /**
670721 * 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).
672723 * Prepares data for {@link #internalApplyToDb(Cursor, Cursor)}
673724 */
674725 @ Internal
@@ -692,7 +743,7 @@ public boolean internalCheckApplyToDbRequired() {
692743 // Relation based on Backlink
693744 long entityId = relationInfo .sourceInfo .getIdGetter ().getId (entity );
694745 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)" );
696747 }
697748 IdGetter <TARGET > idGetter = relationInfo .targetInfo .getIdGetter ();
698749 Map <TARGET , Boolean > setAdded = this .entitiesAdded ;
@@ -866,7 +917,7 @@ public void internalApplyToDb(Cursor<?> sourceCursor, Cursor<TARGET> targetCurso
866917 if (isStandaloneRelation ) {
867918 long entityId = relationInfo .sourceInfo .getIdGetter ().getId (entity );
868919 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)" );
870921 }
871922
872923 if (removedStandalone != null ) {
@@ -909,7 +960,7 @@ private void addStandaloneRelations(Cursor<?> cursor, long sourceEntityId, TARGE
909960 long targetId = targetIdGetter .getId (added [i ]);
910961 if (targetId == 0 ) {
911962 // 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)" );
913964 }
914965 targetIds [i ] = targetId ;
915966 }
0 commit comments