As mentioned in the comments, one workaround I came up with was to create a ComparableList
, by adopting code from ComparableSet
.
The idea was to make a list that is able to convert to and from an ArrayByteIterable
, and register it with .registerCustomPropertyType()
. To do that, 2 classes are needed, ComparableList
and ComparableListBinding
. I'm sharing one iteration I used at the bottom. By the way, I made them immutable, comparing to the mutable ComparableSet
. The newly implemented types should be registered once in a transaction of the store before using them.
That should allow you to store and retrieve a list. However the items in ComparableList
would not get indexed as they would when saved in a ComparableSet
-- there are some special treatment for ComparableSet
in the entity store implementation. So without modifying the library, indexing would work only with hacks like creating another property to just index the values.
I was considering to implement a different entity store that could better support lists, on top of the xodus key-value store, bypassing the xodus entity store entirely. That might be a better solution to the list issue we are talking about here.
ComparableList
:
@SuppressWarnings("unchecked")
public class ComparableList<T extends Comparable<T>> implements Comparable<ComparableList<T>>,
Iterable<T> {
@Nonnull
private final ImmutableList<T> list;
public ComparableList(@Nonnull final Iterable<T> iterable) {
list = ImmutableList.copyOf(iterable);
}
@Override
public int compareTo(@Nonnull final ComparableList<T> other) {
final Iterator<T> thisIt = list.iterator();
final Iterator<T> otherIt = other.list.iterator();
while (thisIt.hasNext() && otherIt.hasNext()) {
final int cmp = thisIt.next().compareTo(otherIt.next());
if (cmp != 0) {
return cmp;
}
}
if (thisIt.hasNext()) {
return 1;
}
if (otherIt.hasNext()) {
return -1;
}
return 0;
}
@NotNull
@Override
public Iterator<T> iterator() {
return list.iterator();
}
@Nullable
public Class<T> getItemClass() {
final Iterator<T> it = list.iterator();
return it.hasNext() ? (Class<T>) it.next().getClass() : null;
}
@Override
public String toString() {
return list.toString();
}
}
ComparableListBinding
:
@SuppressWarnings({"unchecked", "rawtypes"})
public class ComparableListBinding extends ComparableBinding {
public static final ComparableListBinding INSTANCE = new ComparableListBinding();
private ComparableListBinding() {}
@Override
public ComparableList readObject(@NotNull final ByteArrayInputStream stream) {
final int valueTypeId = stream.read() ^ 0x80;
final ComparableBinding itemBinding = ComparableValueType.getPredefinedBinding(valueTypeId);
final ImmutableList.Builder<Comparable> builder = ImmutableList.builder();
while (stream.available() > 0) {
builder.add(itemBinding.readObject(stream));
}
return new ComparableList(builder.build());
}
@Override
public void writeObject(@NotNull final LightOutputStream output,
@NotNull final Comparable object) {
final ComparableList<? extends Comparable> list = (ComparableList) object;
final Class itemClass = list.getItemClass();
if (itemClass == null) {
throw new ExodusException("Attempt to write empty ComparableList");
}
final ComparableValueType type = ComparableValueType.getPredefinedType(itemClass);
output.writeByte(type.getTypeId());
final ComparableBinding itemBinding = type.getBinding();
list.forEach(o -> itemBinding.writeObject(output, o));
}
/**
* De-serializes {@linkplain ByteIterable} entry to a {@code ComparableList} value.
*
* @param entry {@linkplain ByteIterable} instance
* @return de-serialized value
*/
public static ComparableList entryToComparableList(@NotNull final ByteIterable entry) {
return (ComparableList) INSTANCE.entryToObject(entry);
}
/**
* Serializes {@code ComparableList} value to the {@linkplain ArrayByteIterable} entry.
*
* @param object value to serialize
* @return {@linkplain ArrayByteIterable} entry
*/
public static ArrayByteIterable comparableSetToEntry(@NotNull final ComparableList object) {
return INSTANCE.objectToEntry(object);
}
}