Compare commits

...
Author SHA1 Message Date
Rob BygraveandGitHub d92abbbe9c Merge branch 'master' into feature/sequencedSet 2024-02-10 14:13:21 +13:00
Rob Bygrave 39500567c3 Build needs to use Java 21 to support the multi-release jar 2024-02-10 13:29:40 +13:00
Rob Bygrave ded6fd71b2 Refactor rename method getBeanCollectionAdd() -> collectionAdd() 2024-01-12 16:42:58 +13:00
Rob Bygrave cbc4883672 Refactor BeanSet, BeanList, BeanMap replacing setActualSet|List|Map
Replace with collectionAdd() and refresh() methods.
2024-01-12 16:41:32 +13:00
Rob Bygrave 64e227117b Add support for Java 21 SequencedSet and SequencedMap
Such that these can be used in place of Set and Map if desired.
2024-01-12 16:14:08 +13:00
24 changed files with 1217 additions and 108 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
java_version: [11]
java_version: [21]
os: [ubuntu-latest]
steps:
+47
View File
@@ -126,6 +126,53 @@
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>11</release>
</configuration>
</execution>
<execution>
<id>compile-21</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>21</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java21</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
<!-- <manifest>-->
<!-- <addDefaultImplementationEntries>true</addDefaultImplementationEntries>-->
<!-- </manifest>-->
</plugin>
</plugins>
</build>
</project>
@@ -141,14 +141,16 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
/**
* Set the actual underlying list.
* <p>
* This is primarily for the deferred fetching function.
*/
@SuppressWarnings("unchecked")
public void setActualList(List<?> list) {
this.list = (List<E>) list;
public BeanCollectionAdd collectionAdd() {
if (list == null) {
list = new ArrayList<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanList<E> newList) {
setModifyListening(modifyListenMode);
this.list = newList.actualList();
}
/**
@@ -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<K, E> extends AbstractBeanCollection<E> implements Ma
/**
* The underlying map implementation.
*/
private Map<K, E> map;
private LinkedHashMap<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(Map<K, E> map) {
public BeanMap(LinkedHashMap<K, E> map) {
this.map = map;
}
@@ -160,18 +157,23 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
}
/**
* Set the actual underlying map. Used for performing lazy fetch.
*/
public LinkedHashMap<K, E> collectionAdd() {
if (map == null) {
map = new LinkedHashMap<>();
}
return map;
}
@SuppressWarnings("unchecked")
public void setActualMap(Map<?, ?> map) {
this.map = (Map<K, E>) map;
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
setModifyListening(modifyListenMode);
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
}
/**
* Return the actual underlying map.
*/
public Map<K, E> actualMap() {
public LinkedHashMap<K, E> actualMap() {
return map;
}
@@ -3,10 +3,7 @@ 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.Set;
import java.util.*;
/**
* Set capable of lazy loading and modification aware.
@@ -18,12 +15,12 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
/**
* The underlying Set implementation.
*/
private Set<E> set;
private LinkedHashSet<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(Set<E> set) {
public BeanSet(LinkedHashSet<E> set) {
this.set = set;
}
@@ -144,18 +141,22 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
}
}
/**
* Set the underlying set (used for lazy fetch).
*/
@SuppressWarnings("unchecked")
public void setActualSet(Set<?> set) {
this.set = (Set<E>) set;
public BeanCollectionAdd collectionAdd() {
if (set == null) {
set = new LinkedHashSet<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanSet<E> newSet) {
setModifyListening(modifyListenMode);
this.set = newSet.actualSet();
}
/**
* Return the actual underlying set.
*/
public Set<E> actualSet() {
public LinkedHashSet<E> actualSet() {
return set;
}
@@ -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<K, E> extends AbstractBeanCollection<E> implements SequencedMap<K, E> {
private static final long serialVersionUID = 1L;
/**
* The underlying map implementation.
*/
private LinkedHashMap<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(LinkedHashMap<K, E> 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<K, E> 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<K, E> otherMap = (BeanMap<K, E>) 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<K, E> collectionAdd() {
if (map == null) {
map = new LinkedHashMap<>();
}
return map;
}
@SuppressWarnings("unchecked")
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
setModifyListening(modifyListenMode);
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
}
/**
* Return the actual underlying map.
*/
public LinkedHashMap<K, E> actualMap() {
return map;
}
/**
* Returns the collection of beans (map values).
*/
@Override
public Collection<E> actualDetails() {
return map.values();
}
/**
* Returns the map entrySet.
*/
@Override
public Collection<?> actualEntries() {
return map.entrySet();
}
@Override
public String toString() {
if (map == null) {
return "BeanMap<deferred>";
} 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<Entry<K, E>> 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<K> 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<E> values() {
init();
if (readOnly) {
return Collections.unmodifiableCollection(map.values());
}
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
}
@Override
public BeanCollection<E> shallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
@Override
public SequencedMap<K, E> reversed() {
init();
if (modifyListening) {
throw new UnsupportedOperationException("Not supported on modify listening map");
}
return map.reversed();
}
@Override
public Entry<K, E> firstEntry() {
init();
return map.firstEntry();
}
@Override
public Entry<K, E> lastEntry() {
init();
return map.lastEntry();
}
@Override
public Entry<K, E> pollFirstEntry() {
checkReadOnly();
init();
var entry = map.pollFirstEntry();
if (modifyListening && entry != null) {
modifyRemoval(entry.getValue());
}
return entry;
}
@Override
public Entry<K, E> 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<K> sequencedKeySet() {
init();
return map.sequencedKeySet();
}
@Override
public SequencedCollection<E> sequencedValues() {
init();
return map.sequencedValues();
}
@Override
public SequencedSet<Entry<K, E>> sequencedEntrySet() {
init();
return map.sequencedEntrySet();
}
}
@@ -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<E> extends AbstractBeanCollection<E> implements SequencedSet<E>, BeanCollectionAdd {
private static final long serialVersionUID = 1L;
/**
* The underlying Set implementation.
*/
private LinkedHashSet<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(LinkedHashSet<E> 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<E> newSet) {
setModifyListening(modifyListenMode);
this.set = newSet.actualSet();
}
/**
* Return the actual underlying set.
*/
public LinkedHashSet<E> actualSet() {
return set;
}
@Override
public Collection<E> actualDetails() {
return set;
}
@Override
public Collection<?> actualEntries() {
return set;
}
@Override
public String toString() {
if (set == null) {
return "BeanSet<deferred>";
} 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<E> 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> T[] toArray(T[] array) {
init();
//noinspection SuspiciousToArrayCall
return set.toArray(array);
}
@Override
public SequencedSet<E> 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<E> implements Iterator<E>, Serializable {
private static final long serialVersionUID = 2577697326745352605L;
private final Iterator<E> it;
ReadOnlyIterator(Iterator<E> 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<E> shallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -136,13 +136,13 @@ class ToStringBuilderTest {
@Test
void beanSet_null_empty() {
assertThat(toStringFor(new BeanSet<String>(null))).isEqualTo("[]");
assertThat(toStringFor(new BeanSet<String>(Collections.emptySet()))).isEqualTo("[]");
assertThat(toStringFor(new BeanSet<String>(new LinkedHashSet<>()))).isEqualTo("[]");
}
@Test
void beanMap_null_empty() {
assertThat(toStringFor(new BeanMap<String, String>(null))).isEqualTo("{}");
assertThat(toStringFor(new BeanMap<String, String>(Collections.emptyMap()))).isEqualTo("{}");
assertThat(toStringFor(new BeanMap<String, String>(new LinkedHashMap<>()))).isEqualTo("{}");
}
@Test
@@ -159,7 +159,7 @@ class ToStringBuilderTest {
@Test
void beanMap_some() {
Map<String, Recurse> under = new LinkedHashMap<>();
var under = new LinkedHashMap<String, Recurse>();
under.put("a", new Recurse(1, "a"));
under.put("b", new Recurse(2, "b"));
BeanMap<String, Recurse> list = new BeanMap<>(under);
@@ -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<T> extends CQueryCollectionAdd<T> {
* For Map's this needs to take the mapKey.
* </p>
*/
BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey);
BeanCollectionAdd collectionAdd(Object bc, String mapKey);
/**
* Create an empty collection of the correct type without a parent bean.
@@ -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<T> extends BaseCollectionHelp<T> {
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 class BeanListHelp<T> extends BaseCollectionHelp<T> {
return beanList;
}
@SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
BeanList<?> newBeanList = (BeanList<?>) bc;
BeanList<T> newBeanList = (BeanList<T>) 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<T> currentBeanList = (BeanList<T>) currentList;
currentBeanList.refresh(many.modifyListenMode(), newBeanList);
} else {
// replace the entire list with the BeanList
@@ -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<T> extends BaseCollectionHelp<T> {
@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<Object, Object> bm = (BeanMap<Object, Object>) bc;
Map<Object, Object> 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 class BeanMapHelp<T> extends BaseCollectionHelp<T> {
} 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
@@ -655,7 +655,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
private BeanCollectionAdd beanCollectionAdd(Object bc) {
return help.getBeanCollectionAdd(bc, null);
return help.collectionAdd(bc, null);
}
public Object parentId(EntityBean parentBean) {
@@ -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<T> extends BaseCollectionHelp<T> {
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 class BeanSetHelp<T> extends BaseCollectionHelp<T> {
return beanSet;
}
@SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
BeanSet<?> newBeanSet = (BeanSet<?>) bc;
BeanSet<T> newBeanSet = (BeanSet<T>) bc;
Set<?> current = (Set<?>) many.getValue(parentBean);
newBeanSet.setModifyListening(many.modifyListenMode());
if (current == null) {
@@ -83,9 +70,8 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
} 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<T> currentBeanSet = (BeanSet<T>) current;
currentBeanSet.refresh(many.modifyListenMode(), newBeanSet);
} else {
// replace the entire set
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import java.util.LinkedHashMap;
import java.util.Map;
final class ElementHelpMap implements ElementHelp {
@@ -15,7 +14,7 @@ final class ElementHelpMap implements ElementHelp {
private static class Collector implements ElementCollector {
private final Map<Object, Object> map = new LinkedHashMap<>();
private final LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
@Override
public void addElement(Object element) {
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import java.util.LinkedHashSet;
import java.util.Set;
final class ElementHelpSet implements ElementHelp {
@@ -15,7 +14,7 @@ final class ElementHelpSet implements ElementHelp {
private static class Collector implements ElementCollector {
private final Set<Object> set = new LinkedHashSet<>();
private final LinkedHashSet<Object> set = new LinkedHashSet<>();
@Override
public void addElement(Object element) {
@@ -122,7 +122,7 @@ public final class DeployCreateProperties {
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);
@@ -11,14 +11,14 @@ import java.util.Set;
*/
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;
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>java11-oss</artifactId>
<version>3.12</version>
<version>4.0</version>
</parent>
<groupId>io.ebean</groupId>
+2 -2
View File
@@ -19,9 +19,9 @@
<profiles>
<profile>
<id>jdk16plus</id>
<id>jdk21plus</id>
<activation>
<jdk>[17,21]</jdk>
<jdk>21</jdk>
</activation>
<modules>
<module>test-java16</module>
+2 -2
View File
@@ -11,7 +11,7 @@
<artifactId>test-java16</artifactId>
<properties>
<java.release>16</java.release>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<dependencies>
@@ -52,7 +52,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.22.0</tile>
<tile>io.ebean.tile:enhancement:13.26.0</tile>
</tiles>
</configuration>
</plugin>
@@ -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<HiSeq> seqs;
@MapKey(name="key")
@OneToMany(cascade = CascadeType.ALL)
SequencedMap<String, HiMap> map;
public long id() {
return id;
}
public HiBasic setId(long id) {
this.id = id;
return this;
}
public SequencedSet<HiSeq> seqs() {
return seqs;
}
public HiBasic setSeqs(SequencedSet<HiSeq> seqs) {
this.seqs = seqs;
return this;
}
public SequencedMap<String, HiMap> map() {
return map;
}
public HiBasic setMap(SequencedMap<String, HiMap> map) {
this.map = map;
return this;
}
}
@@ -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;
}
}
@@ -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<Course> 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<Course> courses() {
return courses;
}
public HiSeq setCourses(SequencedSet<Course> courses) {
this.courses = courses;
return this;
}
}
@@ -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<String,HiMap>();
map.put("a", new HiMap("a", "b"));
bean.setMap(map);
bean.setSeqs(new LinkedHashSet<>(Set.of(new HiSeq("x"))));
DB.save(bean);
List<HiBasic> 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);
}
}