added setterIterator and jdbc module

This commit is contained in:
Shautvast 2023-05-28 17:30:42 +02:00
parent 4d1ae5acf4
commit f778f0456c
24 changed files with 359 additions and 66 deletions

View file

@ -18,19 +18,9 @@
<dependencies>
<dependency>
<groupId>nl.sanderhautvast</groupId>
<artifactId>contiguous</artifactId>
<artifactId>contiguous-jdbc</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
<version>2.7.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -55,7 +45,7 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.10</version>
<version>2.7.12</version>
<configuration>
<mainClass>nl.sanderhautvast.contiguous.demo.DemoApplication</mainClass>
</configuration>

View file

@ -3,6 +3,7 @@ package nl.sanderhautvast.contiguous.demo.repository;
import lombok.extern.slf4j.Slf4j;
import nl.sanderhautvast.contiguous.ContiguousList;
import nl.sanderhautvast.contiguous.JdbcResults;
import nl.sanderhautvast.contiguous.demo.model.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
@ -21,20 +22,7 @@ public class CustomerRepository {
public ContiguousList<Customer> getAllCustomers() {
return jdbcTemplate.query("select * from customers limit 5", rs -> {
ContiguousList<Customer> customers = new ContiguousList<>(Customer.class);
while (rs.next()) {
Customer customer = Customer.builder()
.name(rs.getString("name"))
.email(rs.getString("email"))
.streetname(rs.getString("streetname"))
.housenumber(rs.getInt("housenumber"))
.city(rs.getString("city"))
.country(rs.getString("country"))
.build();
log.info("{}", customer);
customers.add(customer);
}
return customers;
return JdbcResults.toList(rs, Customer.class);
});
}
}

View file

@ -6,6 +6,7 @@
<groupId>nl.sanderhautvast</groupId>
<artifactId>contiguous-jackson</artifactId>
<version>1.1</version>
<description>Using Jackson to convert Contiguous data to JSON</description>
<modelVersion>4.0.0</modelVersion>

52
jdbc/pom.xml Normal file
View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>nl.sanderhautvast</groupId>
<artifactId>contiguous-jdbc</artifactId>
<version>1.0-SNAPSHOT</version>
<description>JDBC functions for Contiguous data</description>
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>nl.sanderhautvast</groupId>
<artifactId>contiguous</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,80 @@
package nl.sanderhautvast.contiguous;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.function.Function;
/**
* Enables the end user to add values from JDBC to a list of results without creating Objects.
* Every property must have a corresponding field in the ResultSet record (table/view/join etc)
* optionally applying a name mapping if they are not equal
* <p>
* // what about null values?
* // test test test
*/
public class JdbcResults {
/**
* Adds the data to an existing CList
*
* @param result the JDBC ResultSet
* @param list The list to add to
* @throws SQLException when db throws error..
*/
public static void addAll(ResultSet result, ContiguousList<?> list) throws SQLException {
addAll(result, list, Function.identity());
}
/**
* Adds the data to an existing CList.
*
* @param result the JDBC ResultSet
* @param list The list to add to
* @param fieldNameMapper maps the name from the element type property to the actual database column name
* @throws SQLException when db throws error..
*/
public static void addAll(ResultSet result, ContiguousList<?> list, Function<String, String> fieldNameMapper) throws SQLException {
ContiguousList<?>.SetterIterator setterIterator = list.setterIterator();
while (result.next()) {
while (setterIterator.hasNext()) {
ContiguousList<?>.Setter next = setterIterator.next();
String fieldName = next.getFieldName();
Object fieldValue;
if (fieldName != null) {
fieldValue = result.getObject(fieldNameMapper.apply(fieldName));
} else {
// assume single Primitive as Contiguous<String>, so just 1 column in the record
fieldValue = result.getObject(1);
}
next.set(fieldValue);
}
setterIterator.nextRecord();
}
}
/**
* Same as addAll, but creates a new CList.
*
* @param result The CList
* @param elementType the desired Object type
* @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());
}
/**
* Same as addAll, but creates a new CList.
*
* @param result The CList
* @param elementType the desired Object type
* @param fieldNameMapper maps the name from the element type property to the actual database column name
* @throws SQLException when db throws error..
*/
public static <E> ContiguousList<E> toList(ResultSet result, Class<E> elementType, Function<String, String> fieldNameMapper) throws SQLException {
ContiguousList<E> list = new ContiguousList<>(elementType);
addAll(result, list, fieldNameMapper);
return list;
}
}

View file

@ -0,0 +1,71 @@
package nl.sanderhautvast.contiguous;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class JdbcResultsTest {
@Mock
private ResultSet mockResults;
@Test
public void testListOfString() throws SQLException {
when(mockResults.next()).thenReturn(true, false);
when(mockResults.getObject(1)).thenReturn("Zaphod");
List<String> presidents = JdbcResults.toList(mockResults, String.class);
assertFalse(presidents.isEmpty());
assertEquals(1, presidents.size());
String president = presidents.get(0);
assertEquals("Zaphod", president);
}
@Test
public void testListOfBean() throws SQLException {
when(mockResults.next()).thenReturn(true, false);
// the shape of the result equals that of the result (name:String, age:int)
when(mockResults.getObject("name")).thenReturn("Zaphod");
when(mockResults.getObject("age")).thenReturn(42); // coincidence?
List<President> presidents = JdbcResults.toList(mockResults, President.class);
assertFalse(presidents.isEmpty());
assertEquals(1, presidents.size());
President president = presidents.get(0);
assertEquals("Zaphod", president.getName());
assertEquals(42, president.getAge());
}
@Test
public void testNameMapping() throws SQLException {
when(mockResults.next()).thenReturn(true, false);
when(mockResults.getObject("name")).thenReturn("Trillian");
when(mockResults.getObject("realName")).thenReturn("Tricia MacMillan");
Map<String, String> nameMapping = Map.of("name", "name", "earthName", "realName");
List<Scientist> scientists = JdbcResults.toList(mockResults, Scientist.class, nameMapping::get);
assertFalse(scientists.isEmpty());
assertEquals(1, scientists.size());
Scientist scientist = scientists.get(0);
assertEquals("Trillian", scientist.getName());
assertEquals("Tricia MacMillan", scientist.getEarthName());
}
}

View file

@ -0,0 +1,15 @@
package nl.sanderhautvast.contiguous;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class President {
private String name;
private int age;
}

View file

@ -0,0 +1,13 @@
package nl.sanderhautvast.contiguous;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Scientist {
private String name;
private String earthName;
}

View file

@ -6,7 +6,7 @@
<groupId>nl.sanderhautvast</groupId>
<artifactId>contiguous</artifactId>
<description>Datastructures with contiguous storage</description>
<description>Datastructures with contiguous storage. Core library with no external dependencies</description>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

View file

@ -4,8 +4,8 @@ import java.lang.invoke.MethodHandle;
import java.math.BigDecimal;
class BigDecimalHandler extends BuiltinTypeHandler<BigDecimal> {
public BigDecimalHandler(MethodHandle getter, MethodHandle setter) {
super(BigDecimal.class, getter, setter);
public BigDecimalHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(BigDecimal.class, propertyName, getter, setter);
}
@Override

View file

@ -4,8 +4,8 @@ import java.lang.invoke.MethodHandle;
import java.math.BigInteger;
class BigIntegerHandler extends BuiltinTypeHandler<BigInteger> {
public BigIntegerHandler(MethodHandle getter, MethodHandle setter) {
super(BigInteger.class, getter, setter);
public BigIntegerHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(BigInteger.class, propertyName, getter, setter);
}
@Override

View file

@ -12,8 +12,8 @@ import java.lang.invoke.MethodHandle;
* ie. when a bean is added or retrieved from the list
*/
public abstract class BuiltinTypeHandler<T> extends TypeHandler {
public BuiltinTypeHandler(Class<?> type, MethodHandle getter, MethodHandle setter) {
super(type, getter, setter);
public BuiltinTypeHandler(Class<?> type, String name, MethodHandle getter, MethodHandle setter) {
super(type, name, getter, setter);
}
/**
@ -29,6 +29,10 @@ public abstract class BuiltinTypeHandler<T> extends TypeHandler {
store(propertyValue, typedList);
}
void storeValue(Object value, ContiguousList<?> contiguousList) {
store((T)value, contiguousList);
}
private T getValue(Object propertyValue) {
// I don't trust this
if (getter == null) {

View file

@ -7,8 +7,8 @@ import java.lang.invoke.MethodHandle;
*/
class ByteHandler extends BuiltinTypeHandler<Byte> {
public ByteHandler(MethodHandle getter, MethodHandle setter) {
super(Byte.class, getter, setter);
public ByteHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(Byte.class, propertyName, getter, setter);
}
@Override

View file

@ -8,8 +8,8 @@ class CompoundTypeHandler extends TypeHandler {
CompoundTypeHandler(Class<?> type) {
super(type, null,null);
CompoundTypeHandler(Class<?> type, String propertyName) {
super(type, propertyName, null,null);
}

View file

@ -8,15 +8,17 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
//notes:
//1. should find out growth factor of arraylist
//2. elementIndices can be arrayList
// should find out growth factor of arraylist
// investigate data array reuse (pooling, SoftReferences etc)
/**
* Short for Contiguous Layout List, an Experimental List implementation
* Behaves like an ArrayList in that it's resizable and indexed.
* The difference is that it uses an efficiently dehydrated version of the object in a cpu cache friendly, contiguous storage in a bytearray,
* without object instance overhead.
* <p>
* Only uses reflection api on creation of the list.
* Only uses reflection api on creation of the list (and in the get() method, but the end user should employ value
* iteration rather than element iteration using said get method).
* Adding/Retrieving/Deleting depend on VarHandles and are aimed to be O(L) runtime complexity
* where L is the nr of attributes to get/set from the objects (recursively).So O(1) for length of the list
* <p>
@ -35,6 +37,11 @@ import java.util.*;
* Implements java.util.List but some methods are not (yet) implemented mainly because they don't make much sense
* performance-wise, like the indexed add and set methods. They mess with the memory layout. The list is meant to
* be appended at the tail.
* <p>
* What I think is a potential use case is a simple CRUD application.
* Here it would become possible to skip Object (when reading from the database),
* and directly map the results to JSON. Both writing and reading should ideally be faster
* than doing it the regular way.
*/
public class ContiguousList<E> extends NotImplementedList<E> implements List<E> {
@ -52,7 +59,8 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
private int currentElementValueIndex;
private int[] elementIndices = new int[10];
private int[] elementIndices = new int[10]; // avoids autoboxing. Could also use standard ArrayList though
// is there a standard lib IntList??
private int size;
@ -75,7 +83,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
if (PropertyHandlerFactory.isKnownType(type)) {
this.rootHandler = PropertyHandlerFactory.forType(type);
} else {
CompoundTypeHandler compoundType = new CompoundTypeHandler(type);
CompoundTypeHandler compoundType = new CompoundTypeHandler(type, null);//TODO revisit
this.rootHandler = compoundType;
try {
addPropertyHandlersForCompoundType(type, compoundType);
@ -95,11 +103,11 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
MethodHandle setter = lookup.findSetter(type, field.getName(), fieldType);
if (PropertyHandlerFactory.isKnownType(fieldType)) {
BuiltinTypeHandler<?> primitiveType = PropertyHandlerFactory.forType(fieldType, getter, setter);
BuiltinTypeHandler<?> primitiveType = PropertyHandlerFactory.forType(fieldType, field.getName(), getter, setter);
parentCompoundType.addHandler(field.getName(), primitiveType);
} else {
CompoundTypeHandler newParent = new CompoundTypeHandler(fieldType);
CompoundTypeHandler newParent = new CompoundTypeHandler(fieldType, field.getName());
newParent.setGetter(getter);
newParent.setSetter(setter);
parentCompoundType.addChild(field, newParent);
@ -170,7 +178,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
try {
if (rootHandler instanceof BuiltinTypeHandler<?>) {
Object read = ValueReader.read(data);
return (E) ((BuiltinTypeHandler<?>) rootHandler).transform(read);
return (E) ((BuiltinTypeHandler<?>) rootHandler).cast(read);
}
// create a new instance of the list element type
E newInstance = (E) rootHandler.getType().getDeclaredConstructor().newInstance();
@ -295,14 +303,74 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
handler = typeHandlersIterator.next();
return handler.transform(rawValue);
return handler.cast(rawValue);
}
}
/**
* Returns an {@link Iterator} over the property values of the specified element in the List.
* Allows 'iterating insertion of data'. Returns an iterator of Setter
* Does not work for compound types yet
*
* @return
* @return A Reusable iterator
*/
public SetterIterator setterIterator() {
return new SetterIterator();
}
public class Setter {
private final BuiltinTypeHandler<?> currentHandler;
public Setter(BuiltinTypeHandler<?> currentHandler) {
this.currentHandler = currentHandler;
}
public String getFieldName() {
return currentHandler.getName();
}
public void set(Object fieldValue) {
currentHandler.storeValue(fieldValue, ContiguousList.this);
}
}
// TODO proper naming
// BTW do we even need this as a class??
public class SetterIterator implements Iterator<Setter> {
private final List<Setter> properties = new ArrayList<>();
private Iterator<Setter> currentSetterIterator;
public SetterIterator() {
List<BuiltinTypeHandler<?>> builtinTypeHandlers = getBuiltinTypeHandlers();
for (BuiltinTypeHandler<?> builtinTypeHandler : builtinTypeHandlers) {
properties.add(new Setter(builtinTypeHandler));
}
// what to do with compound?
currentSetterIterator = this.properties.iterator();
}
@Override
public boolean hasNext() {
boolean hasNext = currentSetterIterator.hasNext();
if (!hasNext){
extend(); // marks the end of an object
}
return hasNext;
}
@Override
public Setter next() {
return currentSetterIterator.next();
}
public void nextRecord() {
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
@ -402,6 +470,12 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
}
// to be called by framework to force element count
// used by SetterIterator
void extend() {
size += 1;
}
void storeLong(Long value) {
if (value == null) {
store0();

View file

@ -6,8 +6,8 @@ import java.lang.invoke.MethodHandle;
* Stores a double value.
*/
class DoubleHandler extends BuiltinTypeHandler<Double> {
public DoubleHandler(MethodHandle getter, MethodHandle setter) {
super(Double.class, getter, setter);
public DoubleHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(Double.class, propertyName, getter, setter);
}
@Override

View file

@ -3,8 +3,8 @@ package nl.sanderhautvast.contiguous;
import java.lang.invoke.MethodHandle;
class FloatHandler extends BuiltinTypeHandler<Float> {
public FloatHandler(MethodHandle getter, MethodHandle setter) {
super(Float.class, getter, setter);
public FloatHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(Float.class, propertyName, getter, setter);
}
@Override

View file

@ -3,8 +3,8 @@ package nl.sanderhautvast.contiguous;
import java.lang.invoke.MethodHandle;
class IntegerHandler extends BuiltinTypeHandler<Integer> {
public IntegerHandler(MethodHandle getter, MethodHandle setter) {
super(Integer.class, getter, setter);
public IntegerHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(Integer.class, propertyName, getter, setter);
}
@Override

View file

@ -3,8 +3,8 @@ package nl.sanderhautvast.contiguous;
import java.lang.invoke.MethodHandle;
class LongHandler extends BuiltinTypeHandler<Long> {
public LongHandler(MethodHandle getter, MethodHandle setter) {
super(Long.class, getter, setter);
public LongHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(Long.class, propertyName, getter, setter);
}
@Override

View file

@ -41,14 +41,14 @@ final class PropertyHandlerFactory {
return TYPE_HANDLERS.containsKey(type);
}
public static <T> BuiltinTypeHandler<T> forType(Class<T> type, MethodHandle getter, MethodHandle setter) {
public static <T> BuiltinTypeHandler<T> forType(Class<T> type, String name, MethodHandle getter, MethodHandle setter) {
try {
Class<? extends BuiltinTypeHandler<?>> appenderClass = TYPE_HANDLERS.get(type);
if (appenderClass == null) {
throw new IllegalStateException("No Handler for " + type.getName());
}
return (BuiltinTypeHandler<T>) appenderClass.getDeclaredConstructor(MethodHandle.class, MethodHandle.class)
.newInstance(getter, setter);
return (BuiltinTypeHandler<T>) appenderClass.getDeclaredConstructor(String.class, MethodHandle.class, MethodHandle.class)
.newInstance(name, getter, setter);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new IllegalStateException(e);
@ -56,7 +56,7 @@ final class PropertyHandlerFactory {
}
public static <T> BuiltinTypeHandler<T> forType(Class<T> type) {
return forType(type, null, null);
return forType(type, null, null, null);
}
/**

View file

@ -3,8 +3,8 @@ package nl.sanderhautvast.contiguous;
import java.lang.invoke.MethodHandle;
class ShortHandler extends BuiltinTypeHandler<Short> {
public ShortHandler(MethodHandle getter, MethodHandle setter) {
super(Short.class, getter, setter);
public ShortHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(Short.class, propertyName, getter, setter);
}
@Override

View file

@ -3,8 +3,8 @@ package nl.sanderhautvast.contiguous;
import java.lang.invoke.MethodHandle;
class StringHandler extends BuiltinTypeHandler<String> {
public StringHandler(MethodHandle getter, MethodHandle setter) {
super(String.class, getter, setter);
public StringHandler(String propertyName, MethodHandle getter, MethodHandle setter) {
super(String.class, propertyName, getter, setter);
}
@Override

View file

@ -5,7 +5,6 @@ import java.lang.invoke.MethodHandle;
/**
* Abstract basertype over handlers for 'primitives' (ie. long, but also Long,
* String..=> built-in types) and compound types (your own).
*
* Common ancestor primarily to iterator over properties of any type.
* The respective functions are completely different though, and we need `instanceof` to check for the
* actual type. (Rust enums!)
@ -15,9 +14,11 @@ 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;
private final String name;
public TypeHandler(Class<?> type, MethodHandle getter, MethodHandle setter) {
public TypeHandler(Class<?> type, String name, MethodHandle getter, MethodHandle setter) {
this.type = type;
this.name = name;
this.getter = getter;
this.setter = setter;
}
@ -42,4 +43,7 @@ public abstract class TypeHandler {
return type;
}
public String getName() {
return name;
}
}

View file

@ -10,6 +10,7 @@
<modules>
<module>lib</module>
<module>demo</module>
<module>jdbc</module>
<module>jackson</module>
</modules>
<name>contiguous</name>