json output directly from the list...may not remain there

This commit is contained in:
Shautvast 2023-05-31 09:09:53 +02:00
parent db33d0fac8
commit dd6b9c2281
8 changed files with 127 additions and 223 deletions

View file

@ -1,33 +1,28 @@
package nl.sanderhautvast.contiguous;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
public class ListSerializer<E> extends StdSerializer<ContiguousList> {
public class ListSerializer<E> extends StdSerializer<ContiguousList<?>> {
@SuppressWarnings("unchecked")
public ListSerializer() {
super(ContiguousList.class);
super((Class) ContiguousList.class); // ?
}
@Override
public void serialize(
ContiguousList clist, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
ContiguousList<?> clist, JsonGenerator generator, SerializerProvider provider)
throws IOException {
generator.writeStartArray();
if (clist.isSimpleElementType()){
Iterator<?> iterator = clist.valueIterator();
while (iterator.hasNext()){
generator.writeString(iterator.next().toString());
}
} else {
Iterator<String> jsons = clist.jsonIterator();
while (jsons.hasNext()) {
generator.writeRawValue(jsons.next());
}
generator.writeEndArray();

View file

@ -3,7 +3,6 @@ package nl.sanderhautvast.contiguous;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -45,9 +44,9 @@ class ListSerializerTest {
String json = mapper.writeValueAsString(strings);
assertEquals("[{\"name\":\"Vogon constructor fleet\"}," +
"{\"name\":\"Restaurant at the end of the Galaxy\"}," +
"{\"name\":\"Publishing houses of Ursa Minor\"}]",
assertEquals("[{\"name\": \"Vogon constructor fleet\"}," +
"{\"name\": \"Restaurant at the end of the Galaxy\"}," +
"{\"name\": \"Publishing houses of Ursa Minor\"}]",
json);
}
}

View file

@ -57,7 +57,7 @@ public class JdbcResults {
}
next.set(fieldValue);
}
setterIterator.nextRecord();
setterIterator.finishObject();
}
}
@ -69,7 +69,6 @@ public class JdbcResults {
* @throws SQLException when db throws error..
*/
public static <E> ContiguousList<E> toList(ResultSet result, Class<E> elementType) throws SQLException {
ContiguousList<E> list = new ContiguousList<>(elementType);
return toList(result, elementType, Function.identity());
}

View file

@ -24,18 +24,13 @@ public abstract class BuiltinTypeHandler<T> extends TypeHandler {
*/
public abstract void store(T value, ContiguousList<?> list);
@Override
public boolean isBuiltin() {
return true;
}
void storePropertyValue(Object instance, ContiguousList<?> typedList) {
T propertyValue = getValue(instance);
store(propertyValue, typedList);
}
void storeValue(Object value, ContiguousList<?> contiguousList) {
store((T)value, contiguousList);
store((T) value, contiguousList);
}
private T getValue(Object propertyValue) {

View file

@ -6,7 +6,9 @@ import java.util.*;
class CompoundTypeHandler extends TypeHandler {
private final Map<String, TypeHandler> properties = new LinkedHashMap<>();
CompoundTypeHandler(Class<?> type) {
super(type, null,null, null);
}
CompoundTypeHandler(Class<?> type, String propertyName) {
super(type, propertyName, null,null);
@ -24,10 +26,4 @@ class CompoundTypeHandler extends TypeHandler {
void addChild(Field property, CompoundTypeHandler childCompoundType) {
this.properties.put(property.getName(), childCompoundType);
}
@Override
public boolean isBuiltin() {
return false;
}
}

View file

@ -58,7 +58,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
*/
private ByteBuffer data = ByteBuffer.allocate(32);
private int currentElementValueIndex;
private int bufferPosition;
private int[] elementIndices = new int[10]; // avoids autoboxing. Could also use standard ArrayList though
// is there a standard lib IntList??
@ -67,12 +67,9 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
private TypeHandler rootHandler;
private final Map<String, Integer> propertyNames;
public ContiguousList(Class<E> type) {
inspectType(type);
elementIndices[0] = 0; // index of first element
propertyNames = findPropertyNames();
}
public Class<?> getElementType() {
@ -87,14 +84,14 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
* The advantage of the current implementation is that the binary data is not aware of the actual
* object graph. It only knows the 'primitive' values.
*/
private void inspectType(Class<?> type) {
if (PropertyHandlerFactory.isBuiltInType(type)) {
this.rootHandler = PropertyHandlerFactory.forType(type);
private void inspectType(Class<?> elementClass) {
if (PropertyHandlerFactory.isBuiltInType(elementClass)) {
this.rootHandler = PropertyHandlerFactory.forType(elementClass);
} else {
CompoundTypeHandler compoundType = new CompoundTypeHandler(type, null);//TODO revisit
CompoundTypeHandler compoundType = new CompoundTypeHandler(elementClass);
this.rootHandler = compoundType;
try {
addPropertyHandlersForCompoundType(type, compoundType);
addPropertyHandlersForCompoundType(elementClass, compoundType);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
@ -114,7 +111,8 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
MethodHandle setter = lookup.findSetter(type, field.getName(), fieldType);
if (PropertyHandlerFactory.isBuiltInType(fieldType)) {
BuiltinTypeHandler<?> primitiveType = PropertyHandlerFactory.forType(fieldType, field.getName(), getter, setter);
BuiltinTypeHandler<?> primitiveType =
PropertyHandlerFactory.forType(fieldType, field.getName(), getter, setter);
parentCompoundType.addHandler(field.getName(), primitiveType);
} else {
@ -144,7 +142,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
if (elementIndices.length < size + 1) {
this.elementIndices = Arrays.copyOf(this.elementIndices, this.elementIndices.length * 2);
}
elementIndices[size] = currentElementValueIndex;
elementIndices[size] = bufferPosition;
return true;
}
@ -201,7 +199,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
E newInstance = (E) rootHandler.getType().getDeclaredConstructor().newInstance();
// set the data
copyDataIntoNewObjects(newInstance, (CompoundTypeHandler) rootHandler);
copyDataIntoNewObject(newInstance, (CompoundTypeHandler) rootHandler);
return newInstance;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
@ -213,7 +211,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
/*
*
*/
private void copyDataIntoNewObjects(Object element, CompoundTypeHandler compoundType) {
private void copyDataIntoNewObject(Object element, CompoundTypeHandler compoundType) {
compoundType.getProperties().forEach(property -> {
if (property instanceof BuiltinTypeHandler) {
BuiltinTypeHandler<?> type = ((BuiltinTypeHandler<?>) property);
@ -228,7 +226,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
p.getSetter().invokeWithArguments(element, newInstance);
// recurse down
copyDataIntoNewObjects(newInstance, p);
copyDataIntoNewObject(newInstance, p);
} catch (Throwable e) {
throw new RuntimeException(e);
}
@ -251,102 +249,86 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
return new ValueIterator();
}
public Iterator<Property> propertyIterator() {
return new PropertyIterator();
public Iterator<String> jsonIterator() {
return new JsonIterator();
}
static class Property {
String name;
String value;
}
class JsonIterator implements Iterator<String> {
static class PropertyIterator implements Iterator<Property> {
private int index;
@Override
public boolean hasNext() {
return false;
return index < size;
}
@Override
public Property next() {
return null;
public String next() {
return getAsJson(index++);
}
}
/**
* @return a list of types in the Object(graph). So the element type and all (nested) properties
*/
List<Class<?>> getTypes() {
final List<Class<?>> types = new ArrayList<>();
getTypes(rootHandler, types);
return types;
}
private void getTypes(TypeHandler handler, List<Class<?>> types) {
if (handler instanceof BuiltinTypeHandler<?>) {
types.add(handler.getType());
} else {
types.add(handler.getType());
((CompoundTypeHandler) handler).getProperties()
.forEach(propertyHandler -> getTypes(propertyHandler, types));
}
}
public List<String> getPropertyNames() {
return new ArrayList<>(this.propertyNames.keySet());//TODO should be SET!
}
/*
* walk the tree of typehandlers to find the names
* adds an index to know what index a property has
* this in turn is needed to find where the data of the property is in the byte array
* For now the simplest thing I can think of to get this to work
*
* could also store property data indices, but that would incur more memory overhead
* the way it is now, we have to iterate all properties in an element. ie. a tradeoff
* @param index
* @return
*/
private Map<String, Integer> findPropertyNames() {
// no name for the root property
final Map<String, Integer> names = new HashMap<>();
if (rootHandler instanceof CompoundTypeHandler) {
((CompoundTypeHandler) rootHandler).getProperties()
.forEach(propertyHandler -> findPropertyNames(propertyHandler, names, 0));
public String getAsJson(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index <0 or >" + size);
}
return Collections.unmodifiableMap(names);
}
/*
* TODO
* // oopsie: the properties are not guaranteed to be unique
*/
private void findPropertyNames(TypeHandler handler, Map<String, Integer> names, int index) {
if (handler instanceof BuiltinTypeHandler<?>) {
names.put(handler.getName(), index);
} else {
names.put(handler.getName(), index);
for (TypeHandler propertyHandler : ((CompoundTypeHandler) handler).getProperties()) {
findPropertyNames(propertyHandler, names, index++);
}
}
}
/**
* gets a named property of element at index
*
* @param index elementIndex
* @param propertyName the name of the property
* @return the property value
*/
public Object getValue(int index, String propertyName) {
if (rootHandler.isBuiltin() || propertyName == null) {
data.position(elementIndices[index]);
return ValueReader.read(data);
} else {
return null; //TODO implement
if (rootHandler instanceof BuiltinTypeHandler<?>) {
BuiltinTypeHandler<?> handler = (BuiltinTypeHandler<?>) rootHandler;
return getValue(handler);
}
// create a new instance of the list element type
StringBuilder s = new StringBuilder();
s.append("{");
copyDataIntoStringBuilder(s, (CompoundTypeHandler) rootHandler);
s.append("}");
return s.toString();
}
private String getValue(BuiltinTypeHandler<?> handler) {
Object read = ValueReader.read(data);
String out = handler.cast(read).toString();
if (handler instanceof StringHandler) {
out = quote(out);
}
return out;
}
private static String quote(String out) {
StringBuilder s = new StringBuilder();
out = s.append("\"").append(out).append("\"").toString();
return out;
}
/*
*
*/
private void copyDataIntoStringBuilder(StringBuilder s, CompoundTypeHandler compoundType) {
compoundType.getProperties().forEach(property -> {
if (property instanceof BuiltinTypeHandler) {
BuiltinTypeHandler<?> typeHandler = (BuiltinTypeHandler<?>) property;
String name = typeHandler.getName();
String value = getValue(typeHandler);
s.append(quote(name)).append(": ").append(value);
} else {
CompoundTypeHandler p = (CompoundTypeHandler) property;
s.append(p.getName()).append(":{");
// recurse down
copyDataIntoStringBuilder(s, p);
s.append("}");
}
s.append(", ");
});
s.setLength(s.length() - 2);
}
List<BuiltinTypeHandler<?>> getBuiltinTypeHandlers() {
final List<BuiltinTypeHandler<?>> types = new ArrayList<>();
@ -404,15 +386,13 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
}
public boolean isSimpleElementType() {
return PropertyHandlerFactory.isBuiltInType(rootHandler.getType());
}
/**
* Allows 'iterating insertion of data'. Returns an iterator of Setter
* Does not work for compound types yet
* Does not work for compound types yet // (check if still valid)
*
* @return A Reusable iterator
* @return an Iterator over bean property setters. Once an object is completely written,
* call finishObject, to increase the element count and to continue with the next element.
*/
public SetterIterator setterIterator() {
return new SetterIterator();
@ -450,11 +430,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
@Override
public boolean hasNext() {
boolean hasNext = currentSetterIterator.hasNext();
if (!hasNext) {
extend(); // marks the end of an object
}
return hasNext;
return currentSetterIterator.hasNext();
}
@Override
@ -462,18 +438,15 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
return currentSetterIterator.next();
}
public void nextRecord() {
/**
* Indicates that writing of the current object is complete, so
* that it will increment the element count and start a new property iterator
* for the next object (or none it was the last).
*/
public void finishObject() {
extend();
currentSetterIterator = properties.iterator();
}
}
/**
* @return an {@link Iterator} over the property values of the specified element in the List.
*/
public Iterator<Object> valueIterator(int index) {
//TODO
return null;
}
public boolean addAll(Collection<? extends E> collection) {
@ -484,7 +457,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
public void clear() {
this.currentElementValueIndex = 0;
this.bufferPosition = 0;
this.size = 0;
}
@ -496,6 +469,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
public boolean isEmpty() {
return size == 0;
}
@Override
public Object[] toArray() {
Object[] objects = new Object[size];
@ -522,6 +496,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
* Although it's convenient to use, it's not the best way to read from the list because it
* needs the Reflection API to instantiate new objects of the element type.
* <p/>.
*
* @return An Iterator over the elements in the List
*/
@Override
@ -550,15 +525,15 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
private void store(byte[] bytes) {
ensureFree(bytes.length);
data.position(currentElementValueIndex); // ensures intermittent reads/writes are safe
data.position(bufferPosition); // ensures intermittent reads/writes are safe
data.put(bytes);
currentElementValueIndex += bytes.length;
bufferPosition += bytes.length;
}
private void store0() {
ensureFree(1);
data.put((byte) 0);
currentElementValueIndex += 1;
bufferPosition += 1;
}
void storeString(String value) {
@ -636,7 +611,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
byte[] getData() {
return Arrays.copyOfRange(data.array(), 0, currentElementValueIndex);
return Arrays.copyOfRange(data.array(), 0, bufferPosition);
}
int[] getElementIndices() {
@ -644,7 +619,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
private void ensureFree(int length) {
while (currentElementValueIndex + length > data.capacity()) {
while (bufferPosition + length > data.capacity()) {
byte[] bytes = this.data.array();
this.data = ByteBuffer.allocate(this.data.capacity() * 2);
this.data.put(bytes);

View file

@ -14,6 +14,10 @@ public abstract class TypeHandler {
protected MethodHandle getter; // both can be null, if it's for a known ('primitive') type
protected MethodHandle setter;
private final Class<?> type;
/**
* full name, prepended by all parent property names
*/
private final String name;
public TypeHandler(Class<?> type, String name, MethodHandle getter, MethodHandle setter) {
@ -23,8 +27,6 @@ public abstract class TypeHandler {
this.setter = setter;
}
public abstract boolean isBuiltin();
void setGetter(MethodHandle getter) {
this.getter = getter;
}

View file

@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@ -12,7 +11,7 @@ public class ContiguousListTest {
@Test
public void testAddAndGetString() {
List<String> list = new ContiguousList<>(String.class);
ContiguousList<String> list = new ContiguousList<>(String.class);
assertTrue(list.isEmpty());
list.add("hitchhikersguide to the galaxy");
@ -21,6 +20,9 @@ public class ContiguousListTest {
String title = list.get(0);
assertEquals("hitchhikersguide to the galaxy", title);
String titleJson = list.getAsJson(0);
assertEquals("\"hitchhikersguide to the galaxy\"", titleJson);
}
@Test
@ -33,6 +35,9 @@ public class ContiguousListTest {
StringBean douglas = beanList.get(0);
assertEquals("Douglas Adams", douglas.getName());
String douglasJson = beanList.getAsJson(0);
assertEquals("{\"name\": \"Douglas Adams\"}", douglasJson);
// now add new data to see if existing data remains intact
beanList.add(new StringBean("Ford Prefect"));
@ -198,85 +203,23 @@ public class ContiguousListTest {
assertEquals(new IntBean(100), integers.get(100)); // here an instance
}
@Test
public void testGetTypesWhenCompound() {
public void testSetterIterator() {
ContiguousList<NestedBean> integers = new ContiguousList<>(NestedBean.class);
Iterator<Class<?>> typeIterator = integers.getTypes().iterator();
assertTrue(typeIterator.hasNext());
assertEquals(NestedBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(StringBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(String.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(IntBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(Integer.class, typeIterator.next());
ContiguousList<NestedBean>.SetterIterator iterator = integers.setterIterator();
if (iterator.hasNext()) {
iterator.next().set("Magrathea");
}
@Test
public void testGetStoredTypesDeepCompound() {
final ContiguousList<DeepBean> beans = new ContiguousList<>(DeepBean.class);
Iterator<Class<?>> typeIterator = beans.getTypes().iterator();
assertTrue(typeIterator.hasNext());
assertEquals(DeepBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(NestedBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(StringBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(String.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(IntBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(Integer.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(Long.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(StringBean.class, typeIterator.next());
assertTrue(typeIterator.hasNext());
assertEquals(String.class, typeIterator.next());
if (iterator.hasNext()) {
iterator.next().set(42);
}
iterator.finishObject();
@Test
public void testTypeIteratorBuiltin() {
ContiguousList<Integer> integers = new ContiguousList<>(Integer.class);
Iterator<Class<?>> typeIterator = integers.getTypes().iterator();
if (typeIterator.hasNext()) {
assertEquals(Integer.class, typeIterator.next());
}
}
@Test
public void testGetPropertyNamesForBuiltinElementType(){
ContiguousList<Integer> integers = new ContiguousList<>(Integer.class);
assertTrue(integers.getPropertyNames().isEmpty());
}
@Test
public void testGetPropertyNames(){
ContiguousList<IntBean> integers = new ContiguousList<>(IntBean.class);
assertFalse(integers.getPropertyNames().isEmpty());
assertEquals("value", integers.getPropertyNames().get(0));
}
@Test
public void testGetMorePropertyNames(){
ContiguousList<NestedBean> integers = new ContiguousList<>(NestedBean.class);
assertFalse(integers.getPropertyNames().isEmpty());
assertEquals("stringBean", integers.getPropertyNames().get(0));
assertEquals("name", integers.getPropertyNames().get(1));
assertEquals("intBean", integers.getPropertyNames().get(2));
assertEquals("value", integers.getPropertyNames().get(3));
NestedBean nestedBean = integers.get(0);
assertEquals("Magrathea", nestedBean.getStringBean().getName());
assertEquals(42, nestedBean.getIntBean().getValue());
}
}