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 puts) { + checkReadOnly(); + init(); + if (modifyListening) { + for (Entry 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) 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 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 bm = (BeanMap) bc; - Map actualMap = bm.actualMap(); - if (actualMap == null) { - actualMap = new LinkedHashMap<>(); - bm.setActualMap(actualMap); - } - return new Adder(beanProp, actualMap); - + return new Adder(beanProp, bm.collectionAdd()); } else { throw new RuntimeException("Unhandled type " + bc); } @@ -126,8 +116,7 @@ public final void refresh(BeanCollection bc, EntityBean parentBean) { } else if (current instanceof BeanMap) { // normally this case, replace just the underlying list BeanMap currentBeanMap = (BeanMap) current; - currentBeanMap.setActualMap(newBeanMap.actualMap()); - currentBeanMap.setModifyListening(many.modifyListenMode()); + currentBeanMap.refresh(many.modifyListenMode(), newBeanMap); } else { // replace the entire set diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 1c2f3aa9bb..246081e137 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -655,7 +655,7 @@ public BeanCollection createEmpty(EntityBean parentBean) { } private BeanCollectionAdd beanCollectionAdd(Object bc) { - return help.getBeanCollectionAdd(bc, null); + return help.collectionAdd(bc, null); } public Object parentId(EntityBean parentBean) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java index da390bf174..839141b190 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.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.BeanSet; -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.LinkedHashSet; import java.util.Set; /** @@ -26,21 +22,11 @@ public class BeanSetHelp extends BaseCollectionHelp { super(many); } - /** - * For a query that returns a set. - */ - BeanSetHelp() { - super(); - } - @Override - public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { + public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) { if (bc instanceof BeanSet) { BeanSet beanSet = (BeanSet) bc; - if (beanSet.actualSet() == null) { - beanSet.setActualSet(new LinkedHashSet<>()); - } - return beanSet; + return beanSet.collectionAdd(); } else { throw new RuntimeException("Unhandled type " + bc); } @@ -72,9 +58,10 @@ public final BeanCollection createReference(EntityBean parentBean) { return beanSet; } + @SuppressWarnings("unchecked") @Override public final void refresh(BeanCollection bc, EntityBean parentBean) { - BeanSet newBeanSet = (BeanSet) bc; + BeanSet newBeanSet = (BeanSet) bc; Set current = (Set) many.getValue(parentBean); newBeanSet.setModifyListening(many.modifyListenMode()); if (current == null) { @@ -83,9 +70,8 @@ public final void refresh(BeanCollection bc, EntityBean parentBean) { } else if (current instanceof BeanSet) { // normally this case, replace just the underlying list - BeanSet currentBeanSet = (BeanSet) current; - currentBeanSet.setActualSet(newBeanSet.actualSet()); - currentBeanSet.setModifyListening(many.modifyListenMode()); + BeanSet currentBeanSet = (BeanSet) current; + currentBeanSet.refresh(many.modifyListenMode(), newBeanSet); } else { // replace the entire set diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpMap.java index e00d2ef2fd..0cb9b84378 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpMap.java @@ -4,7 +4,6 @@ import io.ebean.common.BeanMap; import java.util.LinkedHashMap; -import java.util.Map; final class ElementHelpMap implements ElementHelp { @@ -15,7 +14,7 @@ public ElementCollector createCollector() { private static class Collector implements ElementCollector { - private final Map map = new LinkedHashMap<>(); + private final LinkedHashMap map = new LinkedHashMap<>(); @Override public void addElement(Object element) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpSet.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpSet.java index e15e74e28b..c944c63511 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpSet.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementHelpSet.java @@ -4,7 +4,6 @@ import io.ebean.common.BeanSet; import java.util.LinkedHashSet; -import java.util.Set; final class ElementHelpSet implements ElementHelp { @@ -15,7 +14,7 @@ public ElementCollector createCollector() { private static class Collector implements ElementCollector { - private final Set set = new LinkedHashSet<>(); + private final LinkedHashSet set = new LinkedHashSet<>(); @Override public void addElement(Object element) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployCreateProperties.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployCreateProperties.java index 4e887bbbc8..75f7bb4d89 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployCreateProperties.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployCreateProperties.java @@ -122,7 +122,7 @@ private DeployBeanProperty createProp(DeployBeanDescriptor desc, Field field) return new DeployBeanProperty(desc, propertyType, field.getGenericType()); } // check for Collection type (list, set or map) - ManyType manyType = determineManyType.getManyType(propertyType); + ManyType manyType = determineManyType.manyType(propertyType); if (manyType != null) { // List, Set or Map based object Class targetType = determineTargetType(field); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java index 40fc6016b3..2e5304fb1b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java @@ -11,14 +11,14 @@ */ final class DetermineManyType { - ManyType getManyType(Class type) { + ManyType manyType(Class type) { if (type.equals(List.class)) { return ManyType.LIST; } - if (type.equals(Set.class)) { + if (type.equals(Set.class) || type.getCanonicalName().equals("java.util.SequencedSet")) { return ManyType.SET; } - if (type.equals(Map.class)) { + if (type.equals(Map.class) || type.getCanonicalName().equals("java.util.SequencedMap")) { return ManyType.MAP; } return null; diff --git a/pom.xml b/pom.xml index be6b987a26..cd1fa5eb10 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.avaje java11-oss - 3.12 + 4.0 io.ebean diff --git a/tests/pom.xml b/tests/pom.xml index bc6ce5b1b7..eeb134231a 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -19,9 +19,9 @@ - jdk16plus + jdk21plus - [17,21] + 21 test-java16 diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml index ed56f55ac5..ba8abc0e14 100644 --- a/tests/test-java16/pom.xml +++ b/tests/test-java16/pom.xml @@ -11,7 +11,7 @@ test-java16 - 16 + 21 @@ -52,7 +52,7 @@ true - io.ebean.tile:enhancement:13.22.0 + io.ebean.tile:enhancement:13.26.0 diff --git a/tests/test-java16/src/main/java/org/example/records/HiBasic.java b/tests/test-java16/src/main/java/org/example/records/HiBasic.java new file mode 100644 index 0000000000..2d7d157fe6 --- /dev/null +++ b/tests/test-java16/src/main/java/org/example/records/HiBasic.java @@ -0,0 +1,47 @@ +package org.example.records; + +import jakarta.persistence.*; + +import java.util.SequencedMap; +import java.util.SequencedSet; + +@Entity +public class HiBasic { + + @Id + long id; + + @OneToMany(cascade = CascadeType.ALL) + SequencedSet seqs; + + @MapKey(name="key") + @OneToMany(cascade = CascadeType.ALL) + SequencedMap map; + + public long id() { + return id; + } + + public HiBasic setId(long id) { + this.id = id; + return this; + } + + public SequencedSet seqs() { + return seqs; + } + + public HiBasic setSeqs(SequencedSet seqs) { + this.seqs = seqs; + return this; + } + + public SequencedMap map() { + return map; + } + + public HiBasic setMap(SequencedMap map) { + this.map = map; + return this; + } +} diff --git a/tests/test-java16/src/main/java/org/example/records/HiMap.java b/tests/test-java16/src/main/java/org/example/records/HiMap.java new file mode 100644 index 0000000000..4b3ed5ee91 --- /dev/null +++ b/tests/test-java16/src/main/java/org/example/records/HiMap.java @@ -0,0 +1,49 @@ +package org.example.records; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +@Entity +public class HiMap { + + @Id + private long id; + + @ManyToOne + private HiBasic parent; + + private final String key; + private final String val; + + public HiMap(String key, String val) { + this.key = key; + this.val = val; + } + + public long id() { + return id; + } + + public HiMap setId(long id) { + this.id = id; + return this; + } + + public HiBasic parent() { + return parent; + } + + public HiMap setParent(HiBasic parent) { + this.parent = parent; + return this; + } + + public String key() { + return key; + } + + public String val() { + return val; + } +} diff --git a/tests/test-java16/src/main/java/org/example/records/HiSeq.java b/tests/test-java16/src/main/java/org/example/records/HiSeq.java new file mode 100644 index 0000000000..a2f679e1eb --- /dev/null +++ b/tests/test-java16/src/main/java/org/example/records/HiSeq.java @@ -0,0 +1,56 @@ +package org.example.records; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +import java.util.SequencedSet; + +@Entity +public class HiSeq { + + @Id + private long id; + + @ManyToOne + private HiBasic parent; + + private final String name; + + private SequencedSet courses; + + public HiSeq(String name) { + this.name = name; + } + + public long id() { + return id; + } + + public HiSeq setId(long id) { + this.id = id; + return this; + } + + public HiBasic parent() { + return parent; + } + + public HiSeq setParent(HiBasic parent) { + this.parent = parent; + return this; + } + + public String name() { + return name; + } + + public SequencedSet courses() { + return courses; + } + + public HiSeq setCourses(SequencedSet courses) { + this.courses = courses; + return this; + } +} diff --git a/tests/test-java16/src/test/java/org/example/records/HiBasicTest.java b/tests/test-java16/src/test/java/org/example/records/HiBasicTest.java new file mode 100644 index 0000000000..13391ecb8c --- /dev/null +++ b/tests/test-java16/src/test/java/org/example/records/HiBasicTest.java @@ -0,0 +1,37 @@ +package org.example.records; + +import io.ebean.DB; +import org.example.records.query.QHiBasic; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class HiBasicTest { + + @Test + void insert() { + + var bean = new HiBasic(); + var map = new LinkedHashMap(); + map.put("a", new HiMap("a", "b")); + bean.setMap(map); + bean.setSeqs(new LinkedHashSet<>(Set.of(new HiSeq("x")))); + + DB.save(bean); + + List list = new QHiBasic() + .seqs.fetch() + .map.fetch() + .findList(); + + assertThat(list).hasSize(1); + HiBasic hiBasic = list.get(0); + assertThat(hiBasic.seqs()).hasSize(1); + assertThat(hiBasic.map()).hasSize(1); + } +}