diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 91ff0bd9bc..1b32cec2a5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- java_version: [11]
+ java_version: [21]
os: [ubuntu-latest]
steps:
diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml
index 0df3871778..1c8514c4dc 100644
--- a/ebean-api/pom.xml
+++ b/ebean-api/pom.xml
@@ -126,6 +126,53 @@
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+
+ compile
+
+
+ 11
+
+
+
+ compile-21
+ compile
+
+ compile
+
+
+ 21
+
+ ${project.basedir}/src/main/java21
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ false
+
+ true
+
+
+
+
+
+
+
+
+
diff --git a/ebean-api/src/main/java/io/ebean/common/BeanList.java b/ebean-api/src/main/java/io/ebean/common/BeanList.java
index 12d0770d62..49b0e520bd 100644
--- a/ebean-api/src/main/java/io/ebean/common/BeanList.java
+++ b/ebean-api/src/main/java/io/ebean/common/BeanList.java
@@ -141,14 +141,16 @@ private void init() {
}
}
- /**
- * Set the actual underlying list.
- *
- * This is primarily for the deferred fetching function.
- */
- @SuppressWarnings("unchecked")
- public void setActualList(List> list) {
- this.list = (List) list;
+ public BeanCollectionAdd collectionAdd() {
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ return this;
+ }
+
+ public void refresh(ModifyListenMode modifyListenMode, BeanList newList) {
+ setModifyListening(modifyListenMode);
+ this.list = newList.actualList();
}
/**
diff --git a/ebean-api/src/main/java/io/ebean/common/BeanMap.java b/ebean-api/src/main/java/io/ebean/common/BeanMap.java
index fe959c22c0..6f2de6e35d 100644
--- a/ebean-api/src/main/java/io/ebean/common/BeanMap.java
+++ b/ebean-api/src/main/java/io/ebean/common/BeanMap.java
@@ -1,9 +1,6 @@
package io.ebean.common;
-import io.ebean.bean.BeanCollection;
-import io.ebean.bean.BeanCollectionLoader;
-import io.ebean.bean.EntityBean;
-import io.ebean.bean.ToStringBuilder;
+import io.ebean.bean.*;
import java.util.*;
@@ -17,12 +14,12 @@ public final class BeanMap extends AbstractBeanCollection implements Ma
/**
* The underlying map implementation.
*/
- private Map map;
+ private LinkedHashMap map;
/**
* Create with a given Map.
*/
- public BeanMap(Map map) {
+ public BeanMap(LinkedHashMap map) {
this.map = map;
}
@@ -160,18 +157,23 @@ private void init() {
}
}
- /**
- * Set the actual underlying map. Used for performing lazy fetch.
- */
+ public LinkedHashMap collectionAdd() {
+ if (map == null) {
+ map = new LinkedHashMap<>();
+ }
+ return map;
+ }
+
@SuppressWarnings("unchecked")
- public void setActualMap(Map, ?> map) {
- this.map = (Map) map;
+ public void refresh(ModifyListenMode modifyListenMode, BeanMap, ?> newMap) {
+ setModifyListening(modifyListenMode);
+ this.map = (LinkedHashMap) newMap.actualMap();
}
/**
* Return the actual underlying map.
*/
- public Map actualMap() {
+ public LinkedHashMap actualMap() {
return map;
}
diff --git a/ebean-api/src/main/java/io/ebean/common/BeanSet.java b/ebean-api/src/main/java/io/ebean/common/BeanSet.java
index dda2b24ee9..52f6da7ddc 100644
--- a/ebean-api/src/main/java/io/ebean/common/BeanSet.java
+++ b/ebean-api/src/main/java/io/ebean/common/BeanSet.java
@@ -3,10 +3,7 @@
import io.ebean.bean.*;
import java.io.Serializable;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.*;
/**
* Set capable of lazy loading and modification aware.
@@ -18,12 +15,12 @@ public final class BeanSet extends AbstractBeanCollection implements Set set;
+ private LinkedHashSet set;
/**
* Create with a specific Set implementation.
*/
- public BeanSet(Set set) {
+ public BeanSet(LinkedHashSet set) {
this.set = set;
}
@@ -144,18 +141,22 @@ private void init() {
}
}
- /**
- * Set the underlying set (used for lazy fetch).
- */
- @SuppressWarnings("unchecked")
- public void setActualSet(Set> set) {
- this.set = (Set) set;
+ public BeanCollectionAdd collectionAdd() {
+ if (set == null) {
+ set = new LinkedHashSet<>();
+ }
+ return this;
+ }
+
+ public void refresh(ModifyListenMode modifyListenMode, BeanSet newSet) {
+ setModifyListening(modifyListenMode);
+ this.set = newSet.actualSet();
}
/**
* Return the actual underlying set.
*/
- public Set actualSet() {
+ public LinkedHashSet actualSet() {
return set;
}
diff --git a/ebean-api/src/main/java21/io/ebean/common/BeanMap.java b/ebean-api/src/main/java21/io/ebean/common/BeanMap.java
new file mode 100644
index 0000000000..69ce2797d6
--- /dev/null
+++ b/ebean-api/src/main/java21/io/ebean/common/BeanMap.java
@@ -0,0 +1,450 @@
+package io.ebean.common;
+
+import io.ebean.bean.BeanCollection;
+import io.ebean.bean.BeanCollectionLoader;
+import io.ebean.bean.EntityBean;
+import io.ebean.bean.ToStringBuilder;
+
+import java.util.*;
+
+/**
+ * Map capable of lazy loading and modification aware.
+ */
+public final class BeanMap extends AbstractBeanCollection implements SequencedMap {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The underlying map implementation.
+ */
+ private LinkedHashMap map;
+
+ /**
+ * Create with a given Map.
+ */
+ public BeanMap(LinkedHashMap map) {
+ this.map = map;
+ }
+
+ /**
+ * Create using a underlying LinkedHashMap.
+ */
+ public BeanMap() {
+ this(new LinkedHashMap<>());
+ }
+
+ public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) {
+ super(ebeanServer, ownerBean, propertyName);
+ }
+
+ @Override
+ public void toString(ToStringBuilder builder) {
+ if (map == null || map.isEmpty()) {
+ builder.addRaw("{}");
+ } else {
+ builder.addRaw("{");
+ for (Entry entry : map.entrySet()) {
+ builder.add(String.valueOf(entry.getKey()), entry.getValue());
+ }
+ builder.addRaw("}");
+ }
+ }
+
+ @Override
+ public void reset(EntityBean ownerBean, String propertyName) {
+ this.ownerBean = ownerBean;
+ this.propertyName = propertyName;
+ this.map = null;
+ }
+
+ @Override
+ public boolean isSkipSave() {
+ return map == null || (map.isEmpty() && !holdsModifications());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void loadFrom(BeanCollection> other) {
+ BeanMap otherMap = (BeanMap) other;
+ internalPutNull();
+ map.putAll(otherMap.actualMap());
+ }
+
+ public void internalPutNull() {
+ if (map == null) {
+ map = new LinkedHashMap<>();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void internalPut(Object key, Object bean) {
+ if (map == null) {
+ map = new LinkedHashMap<>();
+ }
+ if (key != null) {
+ map.put((K) key, (E) bean);
+ }
+ }
+
+ public void internalPutWithCheck(Object key, Object bean) {
+ if (map == null || key == null || !map.containsKey(key)) {
+ internalPut(key, bean);
+ }
+ }
+
+ @Override
+ public void internalAddWithCheck(Object bean) {
+ throw new RuntimeException("Not allowed for map");
+ }
+
+ @Override
+ public void internalAdd(Object bean) {
+ throw new RuntimeException("Not allowed for map");
+ }
+
+ /**
+ * Return true if the underlying map has been populated. Returns false if it
+ * has a deferred fetch pending.
+ */
+ @Override
+ public boolean isPopulated() {
+ return map != null;
+ }
+
+ /**
+ * Return true if this is a reference (lazy loading) bean collection. This is
+ * the same as !isPopulated();
+ */
+ @Override
+ public boolean isReference() {
+ return map == null;
+ }
+
+ @Override
+ public boolean checkEmptyLazyLoad() {
+ if (map == null) {
+ map = new LinkedHashMap<>();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void initClear() {
+ lock.lock();
+ try {
+ if (map == null) {
+ if (!disableLazyLoad && modifyListening) {
+ lazyLoadCollection(true);
+ } else {
+ map = new LinkedHashMap<>();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void init() {
+ lock.lock();
+ try {
+ if (map == null) {
+ if (disableLazyLoad) {
+ map = new LinkedHashMap<>();
+ } else {
+ lazyLoadCollection(false);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public LinkedHashMap collectionAdd() {
+ if (map == null) {
+ map = new LinkedHashMap<>();
+ }
+ return map;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void refresh(ModifyListenMode modifyListenMode, BeanMap, ?> newMap) {
+ setModifyListening(modifyListenMode);
+ this.map = (LinkedHashMap) newMap.actualMap();
+ }
+
+ /**
+ * Return the actual underlying map.
+ */
+ public LinkedHashMap actualMap() {
+ return map;
+ }
+
+ /**
+ * Returns the collection of beans (map values).
+ */
+ @Override
+ public Collection actualDetails() {
+ return map.values();
+ }
+
+ /**
+ * Returns the map entrySet.
+ */
+ @Override
+ public Collection> actualEntries() {
+ return map.entrySet();
+ }
+
+ @Override
+ public String toString() {
+ if (map == null) {
+ return "BeanMap";
+ } else {
+ return map.toString();
+ }
+ }
+
+ /**
+ * Equal if object is a Map and equal in a Map sense.
+ */
+ @Override
+ public boolean equals(Object object) {
+ init();
+ return map.equals(object);
+ }
+
+ @Override
+ public int hashCode() {
+ init();
+ return map.hashCode();
+ }
+
+ @Override
+ public void clear() {
+ checkReadOnly();
+ initClear();
+ if (modifyListening) {
+ // add all beans to the removal list
+ for (E bean : map.values()) {
+ modifyRemoval(bean);
+ }
+ }
+ map.clear();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ init();
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ init();
+ return map.containsValue(value);
+ }
+
+ @Override
+ public Set> entrySet() {
+ init();
+ if (readOnly) {
+ return Collections.unmodifiableSet(map.entrySet());
+ }
+ return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
+ }
+
+ @Override
+ public E get(Object key) {
+ init();
+ return map.get(key);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ init();
+ return map.isEmpty();
+ }
+
+ @Override
+ public Set keySet() {
+ init();
+ if (readOnly) {
+ return Collections.unmodifiableSet(map.keySet());
+ }
+ return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
+ }
+
+ @Override
+ public E put(K key, E value) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ E oldBean = map.put(key, value);
+ if (value != oldBean) {
+ // register the add of the new and the removal of the old
+ modifyAddition(value);
+ modifyRemoval(oldBean);
+ }
+ return oldBean;
+ } else {
+ return map.put(key, value);
+ }
+ }
+
+ @Override
+ public void putAll(Map extends K, ? extends E> puts) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ for (Entry extends K, ? extends E> entry : puts.entrySet()) {
+ Object oldBean = map.put(entry.getKey(), entry.getValue());
+ if (entry.getValue() != oldBean) {
+ modifyAddition(entry.getValue());
+ modifyRemoval(oldBean);
+ }
+ }
+ } else {
+ map.putAll(puts);
+ }
+ }
+
+ @Override
+ public void addBean(E bean) {
+ throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
+ }
+
+ @Override
+ public void removeBean(E bean) {
+ throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
+ }
+
+ @Override
+ public E remove(Object key) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ E o = map.remove(key);
+ modifyRemoval(o);
+ return o;
+ }
+ return map.remove(key);
+ }
+
+ @Override
+ public int size() {
+ init();
+ return map.size();
+ }
+
+ @Override
+ public Collection values() {
+ init();
+ if (readOnly) {
+ return Collections.unmodifiableCollection(map.values());
+ }
+ return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
+ }
+
+ @Override
+ public BeanCollection shallowCopy() {
+ BeanMap copy = new BeanMap<>(new LinkedHashMap<>(map));
+ copy.setFromOriginal(this);
+ return copy;
+ }
+
+ @Override
+ public SequencedMap reversed() {
+ init();
+ if (modifyListening) {
+ throw new UnsupportedOperationException("Not supported on modify listening map");
+ }
+ return map.reversed();
+ }
+
+ @Override
+ public Entry firstEntry() {
+ init();
+ return map.firstEntry();
+ }
+
+ @Override
+ public Entry lastEntry() {
+ init();
+ return map.lastEntry();
+ }
+
+ @Override
+ public Entry pollFirstEntry() {
+ checkReadOnly();
+ init();
+ var entry = map.pollFirstEntry();
+ if (modifyListening && entry != null) {
+ modifyRemoval(entry.getValue());
+ }
+ return entry;
+ }
+
+ @Override
+ public Entry pollLastEntry() {
+ checkReadOnly();
+ init();
+ var entry = map.pollLastEntry();
+ if (modifyListening && entry != null) {
+ modifyRemoval(entry.getValue());
+ }
+ return entry;
+ }
+
+ @Override
+ public E putFirst(K key, E value) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ E oldBean = map.putFirst(key, value);
+ if (value != oldBean) {
+ // register the add of the new and the removal of the old
+ modifyAddition(value);
+ modifyRemoval(oldBean);
+ }
+ return oldBean;
+ } else {
+ return map.putFirst(key, value);
+ }
+ }
+
+ @Override
+ public E putLast(K key, E value) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ E oldBean = map.putLast(key, value);
+ if (value != oldBean) {
+ // register the add of the new and the removal of the old
+ modifyAddition(value);
+ modifyRemoval(oldBean);
+ }
+ return oldBean;
+ } else {
+ return map.putLast(key, value);
+ }
+ }
+
+ @Override
+ public SequencedSet sequencedKeySet() {
+ init();
+ return map.sequencedKeySet();
+ }
+
+ @Override
+ public SequencedCollection sequencedValues() {
+ init();
+ return map.sequencedValues();
+ }
+
+ @Override
+ public SequencedSet> sequencedEntrySet() {
+ init();
+ return map.sequencedEntrySet();
+ }
+}
diff --git a/ebean-api/src/main/java21/io/ebean/common/BeanSet.java b/ebean-api/src/main/java21/io/ebean/common/BeanSet.java
new file mode 100644
index 0000000000..63dcd5755c
--- /dev/null
+++ b/ebean-api/src/main/java21/io/ebean/common/BeanSet.java
@@ -0,0 +1,460 @@
+package io.ebean.common;
+
+import io.ebean.bean.*;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.SequencedSet;
+
+/**
+ * Set capable of lazy loading and modification aware.
+ */
+public final class BeanSet extends AbstractBeanCollection implements SequencedSet, BeanCollectionAdd {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The underlying Set implementation.
+ */
+ private LinkedHashSet set;
+
+ /**
+ * Create with a specific Set implementation.
+ */
+ public BeanSet(LinkedHashSet set) {
+ this.set = set;
+ }
+
+ /**
+ * Create using an underlying LinkedHashSet.
+ */
+ public BeanSet() {
+ this(new LinkedHashSet<>());
+ }
+
+ public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) {
+ super(loader, ownerBean, propertyName);
+ }
+
+ @Override
+ public void toString(ToStringBuilder builder) {
+ builder.addCollection(set);
+ }
+
+ @Override
+ public void reset(EntityBean ownerBean, String propertyName) {
+ this.ownerBean = ownerBean;
+ this.propertyName = propertyName;
+ this.set = null;
+ }
+
+ @Override
+ public boolean isSkipSave() {
+ return set == null || (set.isEmpty() && !holdsModifications());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void addEntityBean(EntityBean bean) {
+ set.add((E) bean);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void loadFrom(BeanCollection> other) {
+ if (set == null) {
+ set = new LinkedHashSet<>();
+ }
+ set.addAll((Collection extends E>) other.actualDetails());
+ }
+
+ @Override
+ public void internalAddWithCheck(Object bean) {
+ // set add() already de-dups so just add it
+ internalAdd(bean);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void internalAdd(Object bean) {
+ if (set == null) {
+ set = new LinkedHashSet<>();
+ }
+ if (bean != null) {
+ set.add((E) bean);
+ }
+ }
+
+ /**
+ * Returns true if the underlying set has its data.
+ */
+ @Override
+ public boolean isPopulated() {
+ return set != null;
+ }
+
+ /**
+ * Return true if this is a reference (lazy loading) bean collection. This is
+ * the same as !isPopulated();
+ */
+ @Override
+ public boolean isReference() {
+ return set == null;
+ }
+
+ @Override
+ public boolean checkEmptyLazyLoad() {
+ if (set == null) {
+ set = new LinkedHashSet<>();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void initClear() {
+ lock.lock();
+ try {
+ if (set == null) {
+ if (!disableLazyLoad && modifyListening) {
+ lazyLoadCollection(false);
+ } else {
+ set = new LinkedHashSet<>();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void init() {
+ lock.lock();
+ try {
+ if (set == null) {
+ if (disableLazyLoad) {
+ set = new LinkedHashSet<>();
+ } else {
+ lazyLoadCollection(false);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public BeanCollectionAdd collectionAdd() {
+ if (set == null) {
+ set = new LinkedHashSet<>();
+ }
+ return this;
+ }
+
+ public void refresh(ModifyListenMode modifyListenMode, BeanSet newSet) {
+ setModifyListening(modifyListenMode);
+ this.set = newSet.actualSet();
+ }
+
+ /**
+ * Return the actual underlying set.
+ */
+ public LinkedHashSet actualSet() {
+ return set;
+ }
+
+ @Override
+ public Collection actualDetails() {
+ return set;
+ }
+
+ @Override
+ public Collection> actualEntries() {
+ return set;
+ }
+
+ @Override
+ public String toString() {
+ if (set == null) {
+ return "BeanSet";
+ } else {
+ return set.toString();
+ }
+ }
+
+ /**
+ * Equal if obj is a Set and equal in a Set sense.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ init();
+ return set.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ init();
+ return set.hashCode();
+ }
+
+ @Override
+ public void addBean(E bean) {
+ add(bean);
+ }
+
+ @Override
+ public void removeBean(E bean) {
+ if (set.remove(bean)) {
+ getModifyHolder().modifyRemoval(bean);
+ }
+ }
+
+ // -----------------------------------------------------//
+ // proxy method for map
+ // -----------------------------------------------------//
+
+ @Override
+ public boolean add(E bean) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ if (set.add(bean)) {
+ modifyAddition(bean);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return set.add(bean);
+ }
+
+ @Override
+ public boolean addAll(Collection extends E> beans) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ boolean changed = false;
+ for (E bean : beans) {
+ if (set.add(bean)) {
+ // register the addition of the bean
+ modifyAddition(bean);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+ return set.addAll(beans);
+ }
+
+ @Override
+ public void clear() {
+ checkReadOnly();
+ initClear();
+ if (modifyListening) {
+ for (E bean : set) {
+ modifyRemoval(bean);
+ }
+ }
+ set.clear();
+ }
+
+ @Override
+ public boolean contains(Object bean) {
+ init();
+ return set.contains(bean);
+ }
+
+ @Override
+ public boolean containsAll(Collection> beans) {
+ init();
+ return set.containsAll(beans);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ init();
+ return set.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator() {
+ init();
+ if (readOnly) {
+ return new ReadOnlyIterator<>(set.iterator());
+ }
+ if (modifyListening) {
+ return new ModifyIterator<>(this, set.iterator());
+ }
+ return set.iterator();
+ }
+
+ @Override
+ public boolean remove(Object bean) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ if (set.remove(bean)) {
+ modifyRemoval(bean);
+ return true;
+ }
+ return false;
+ }
+ return set.remove(bean);
+ }
+
+ @Override
+ public boolean removeAll(Collection> beans) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ boolean changed = false;
+ for (Object bean : beans) {
+ if (set.remove(bean)) {
+ modifyRemoval(bean);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+ return set.removeAll(beans);
+ }
+
+ @Override
+ public boolean retainAll(Collection> beans) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ boolean changed = false;
+ Iterator> it = set.iterator();
+ while (it.hasNext()) {
+ Object bean = it.next();
+ if (!beans.contains(bean)) {
+ // not retaining this bean so add it to the removal list
+ it.remove();
+ modifyRemoval(bean);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+ return set.retainAll(beans);
+ }
+
+ @Override
+ public int size() {
+ init();
+ return set.size();
+ }
+
+ @Override
+ public Object[] toArray() {
+ init();
+ return set.toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] array) {
+ init();
+ //noinspection SuspiciousToArrayCall
+ return set.toArray(array);
+ }
+
+
+ @Override
+ public SequencedSet reversed() {
+ init();
+ if (modifyListening) {
+ throw new UnsupportedOperationException("Not supported on modify listening set");
+ }
+ return set.reversed();
+ }
+
+ @Override
+ public void addFirst(E bean) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ modifyAddition(bean);
+ }
+ set.addFirst(bean);
+ }
+
+ @Override
+ public void addLast(E bean) {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ modifyAddition(bean);
+ }
+ set.addLast(bean);
+ }
+
+ @Override
+ public E getFirst() {
+ init();
+ return set.getFirst();
+ }
+
+ @Override
+ public E getLast() {
+ init();
+ return set.getLast();
+ }
+
+ @Override
+ public E removeFirst() {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ var bean = set.removeFirst();
+ modifyRemoval(bean);
+ return bean;
+ }
+ return set.removeFirst();
+ }
+
+ @Override
+ public E removeLast() {
+ checkReadOnly();
+ init();
+ if (modifyListening) {
+ var bean = set.removeLast();
+ modifyRemoval(bean);
+ return bean;
+ }
+ return set.removeLast();
+ }
+
+ private static final class ReadOnlyIterator implements Iterator, Serializable {
+
+ private static final long serialVersionUID = 2577697326745352605L;
+
+ private final Iterator it;
+
+ ReadOnlyIterator(Iterator it) {
+ this.it = it;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public E next() {
+ return it.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new IllegalStateException("This collection is in ReadOnly mode");
+ }
+ }
+
+ @Override
+ public BeanCollection shallowCopy() {
+ BeanSet copy = new BeanSet<>(new LinkedHashSet<>(set));
+ copy.setFromOriginal(this);
+ return copy;
+ }
+}
diff --git a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java
index c049ec9b0e..fad27d5d05 100644
--- a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java
+++ b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java
@@ -136,13 +136,13 @@ void beanList_null_empty() {
@Test
void beanSet_null_empty() {
assertThat(toStringFor(new BeanSet(null))).isEqualTo("[]");
- assertThat(toStringFor(new BeanSet(Collections.emptySet()))).isEqualTo("[]");
+ assertThat(toStringFor(new BeanSet(new LinkedHashSet<>()))).isEqualTo("[]");
}
@Test
void beanMap_null_empty() {
assertThat(toStringFor(new BeanMap(null))).isEqualTo("{}");
- assertThat(toStringFor(new BeanMap(Collections.emptyMap()))).isEqualTo("{}");
+ assertThat(toStringFor(new BeanMap(new LinkedHashMap<>()))).isEqualTo("{}");
}
@Test
@@ -159,7 +159,7 @@ void beanSet_some() {
@Test
void beanMap_some() {
- Map under = new LinkedHashMap<>();
+ var under = new LinkedHashMap();
under.put("a", new Recurse(1, "a"));
under.put("b", new Recurse(2, "b"));
BeanMap list = new BeanMap<>(under);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java
index 4a410f90ca..4a44244fac 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java
@@ -1,12 +1,9 @@
package io.ebeaninternal.server.deploy;
-import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
-import io.ebeaninternal.api.SpiEbeanServer;
-import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
@@ -34,7 +31,7 @@ public interface BeanCollectionHelp extends CQueryCollectionAdd {
* For Map's this needs to take the mapKey.
*
*/
- BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey);
+ BeanCollectionAdd collectionAdd(Object bc, String mapKey);
/**
* Create an empty collection of the correct type without a parent bean.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java
index dc4a8c520f..f9b0b39e17 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java
@@ -1,16 +1,12 @@
package io.ebeaninternal.server.deploy;
-import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
-import io.ebeaninternal.api.SpiEbeanServer;
-import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -23,19 +19,11 @@ public class BeanListHelp extends BaseCollectionHelp {
super(many);
}
- BeanListHelp() {
- super();
- }
-
@Override
- public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
+ public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanList>) {
BeanList> bl = (BeanList>) bc;
- if (bl.actualList() == null) {
- bl.setActualList(new ArrayList<>());
- }
- return bl;
-
+ return bl.collectionAdd();
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -67,20 +55,20 @@ public final BeanCollection createReference(EntityBean parentBean) {
return beanList;
}
+ @SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection> bc, EntityBean parentBean) {
- BeanList> newBeanList = (BeanList>) bc;
+ BeanList newBeanList = (BeanList) bc;
List> currentList = (List>) many.getValue(parentBean);
newBeanList.setModifyListening(many.modifyListenMode());
if (currentList == null) {
// the currentList is null? Not really expecting this...
many.setValue(parentBean, newBeanList);
- } else if (currentList instanceof BeanList>) {
+ } else if (currentList instanceof BeanList) {
// normally this case, replace just the underlying list
- BeanList> currentBeanList = (BeanList>) currentList;
- currentBeanList.setActualList(newBeanList.actualList());
- currentBeanList.setModifyListening(many.modifyListenMode());
+ BeanList currentBeanList = (BeanList) currentList;
+ currentBeanList.refresh(many.modifyListenMode(), newBeanList);
} else {
// replace the entire list with the BeanList
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java
index 041a6ab7d7..cf108954ef 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java
@@ -1,17 +1,13 @@
package io.ebeaninternal.server.deploy;
-import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
-import io.ebeaninternal.api.SpiEbeanServer;
-import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -37,20 +33,14 @@ public class BeanMapHelp extends BaseCollectionHelp {
@Override
@SuppressWarnings("unchecked")
- public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
+ public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
if (mapKey == null) {
mapKey = many.mapKey();
}
BeanProperty beanProp = targetDescriptor.beanProperty(mapKey);
if (bc instanceof BeanMap, ?>) {
BeanMap