From 90b5e28a5a4d9be8f75075b2dee265164fa4448a Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Mon, 15 Jul 2024 11:26:48 +0200 Subject: [PATCH 01/10] In the process fo rewriting reflection logic and adding support for records --- core/pom.xml | 4 +- .../sql2o/DefaultResultSetHandlerFactory.java | 346 ++++++++++-------- ...DefaultResultSetHandlerFactoryBuilder.java | 16 +- .../main/java/org/sql2o/NamingConvention.java | 17 + core/src/main/java/org/sql2o/Settings.java | 22 ++ .../org/sql2o/reflection/FactoryFacade.java | 22 -- .../main/java/org/sql2o/reflection/Pojo.java | 2 +- .../org/sql2o/reflection/PojoMetadata.java | 11 +- .../sql2o/reflection2/ObjectBuildable.java | 8 + .../reflection2/ObjectBuildableFactory.java | 17 + .../ObjectBuildableFactoryDelegate.java | 5 + .../org/sql2o/reflection2/PojoBuilder.java | 27 ++ .../org/sql2o/reflection2/PojoMetadata.java | 130 +++++++ .../org/sql2o/reflection2/PojoProperty.java | 71 ++++ .../reflection2/PojoPropertyBuilder.java | 41 +++ .../org/sql2o/reflection2/RecordBuilder.java | 56 +++ core/src/test/java/org/sql2o/Sql2oTest.java | 25 -- .../java/org/sql2o/records/RecordsTest.java | 57 +++ .../reflect/ReadColumnAnnotationTest.java | 2 +- .../extensions/postgres/PostgresTest.java | 18 + pom.xml | 2 + 21 files changed, 682 insertions(+), 217 deletions(-) create mode 100644 core/src/main/java/org/sql2o/NamingConvention.java create mode 100644 core/src/main/java/org/sql2o/Settings.java create mode 100644 core/src/main/java/org/sql2o/reflection2/ObjectBuildable.java create mode 100644 core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java create mode 100644 core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactoryDelegate.java create mode 100644 core/src/main/java/org/sql2o/reflection2/PojoBuilder.java create mode 100644 core/src/main/java/org/sql2o/reflection2/PojoMetadata.java create mode 100644 core/src/main/java/org/sql2o/reflection2/PojoProperty.java create mode 100644 core/src/main/java/org/sql2o/reflection2/PojoPropertyBuilder.java create mode 100644 core/src/main/java/org/sql2o/reflection2/RecordBuilder.java create mode 100644 core/src/test/java/org/sql2o/records/RecordsTest.java diff --git a/core/pom.xml b/core/pom.xml index 36cfec76..c459a2cd 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -94,8 +94,8 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 17 + 17 diff --git a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java index 4097c040..1c37a082 100644 --- a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java +++ b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java @@ -6,6 +6,9 @@ import org.sql2o.reflection.Pojo; import org.sql2o.reflection.PojoMetadata; import org.sql2o.reflection.Setter; +import org.sql2o.reflection2.ObjectBuildable; +import org.sql2o.reflection2.ObjectBuildableFactory; +import org.sql2o.reflection2.ObjectBuildableFactoryDelegate; import org.sql2o.tools.AbstractCache; import java.sql.ResultSet; @@ -14,174 +17,209 @@ public class DefaultResultSetHandlerFactory implements ResultSetHandlerFactory { - private final PojoMetadata metadata; private final Quirks quirks; + private final ObjectBuildableFactoryDelegate objectBuilderDelegate; - public DefaultResultSetHandlerFactory(PojoMetadata pojoMetadata, Quirks quirks) { - this.metadata = pojoMetadata; + public DefaultResultSetHandlerFactory(ObjectBuildableFactoryDelegate objectBuilderDelegate, Quirks quirks) { + this.objectBuilderDelegate = objectBuilderDelegate; this.quirks = quirks; } - @SuppressWarnings("unchecked") - private static Setter getSetter( - final Quirks quirks, - final String propertyPath, - final PojoMetadata metadata) { - int index = propertyPath.indexOf('.'); - if (index <= 0) { - // Simple path - fast way - final Setter setter = metadata.getPropertySetterIfExists(propertyPath); - // behavior change: do not throw if POJO contains less properties - if (setter == null) return null; - final Converter converter = quirks.converterOf(setter.getType()); - // setter without converter - if (converter == null) return setter; - return new Setter() { - public void setProperty(Object obj, Object value) { - try { - setter.setProperty(obj, converter.convert(value)); - } catch (ConverterException e) { - throw new Sql2oException("Error trying to convert column " + propertyPath + " to type " + setter.getType(), e); - } - } +// @SuppressWarnings("unchecked") +// private static Setter getSetter( +// final Quirks quirks, +// final String propertyPath, +// final PojoMetadata metadata) { +// int index = propertyPath.indexOf('.'); +// if (index <= 0) { +// // Simple path - fast way +// final Setter setter = metadata.getPropertySetterIfExists(propertyPath); +// // behavior change: do not throw if POJO contains less properties +// if (setter == null) return null; +// final Converter converter = quirks.converterOf(setter.getType()); +// // setter without converter +// if (converter == null) return setter; +// return new Setter() { +// public void setProperty(Object obj, Object value) { +// try { +// setter.setProperty(obj, converter.convert(value)); +// } catch (ConverterException e) { +// throw new Sql2oException("Error trying to convert column " + propertyPath + " to type " + setter.getType(), e); +// } +// } +// +// public Class getType() { +// return setter.getType(); +// } +// }; +// } +// // dot path - long way +// // i'm too lazy now to rewrite this case so I just call old unoptimized code... +// // TODO: rewrite, get rid of POJO class +// return new Setter() { +// public void setProperty(Object obj, Object value) { +// Pojo pojo = new Pojo(metadata, metadata.isCaseSensitive(), obj); +// pojo.setProperty(propertyPath, value, quirks); +// } +// +// public Class getType() { +// // doesn't used anyway +// return Object.class; +// } +// }; +// } + +// private static class Key { +// final String stringKey; +// final DefaultResultSetHandlerFactory f; +// +// DefaultResultSetHandlerFactory factory(){ +// return f; +// } +// +// private PojoMetadata getMetadata() { +// return f.metadata; +// } +// +// private Quirks getQuirksMode() { +// return f.quirks; +// } +// +// private Key(String stringKey, DefaultResultSetHandlerFactory f) { +// this.stringKey = stringKey; +// this.f = f; +// } +// +// @Override +// public boolean equals(Object o) { +// if (this == o) return true; +// if (o == null || getClass() != o.getClass()) return false; +// +// Key key = (Key) o; +// +// return f.metadata.equals(key.getMetadata()) +// && f.quirks == key.getQuirksMode() +// && stringKey.equals(key.stringKey); +// +// } +// +// @Override +// public int hashCode() { +// int result = f.metadata.hashCode(); +// result = 31 * result + f.quirks.hashCode(); +// result = 31 * result + stringKey.hashCode(); +// return result; +// } +// } +// +// +// private static final AbstractCache cache = +// new AbstractCache() { +// @Override +// protected ResultSetHandler evaluate(Key key, ResultSetMetaData param) { +// try { +// return key.factory().newResultSetHandler0(param); +// } catch (SQLException e) { +// throw new RuntimeException(e); +// } +// } +// }; - public Class getType() { - return setter.getType(); + @SuppressWarnings("unchecked") + public ResultSetHandler newResultSetHandler(final ResultSetMetaData meta) { +// StringBuilder stringBuilder = new StringBuilder(); +// for (int i = 1; i <= meta.getColumnCount(); i++) { +// stringBuilder.append(quirks.getColumnName(meta,i)).append("\n"); +// } +// return cache.get(new Key(stringBuilder.toString(), this),meta); + + return resultSet -> { + + final var objectBuilder = objectBuilderDelegate.newObjectBuilder(); + + for (int i = 1; i <= meta.getColumnCount(); i++) { + final var colName = quirks.getColumnName(meta, i); + try { + objectBuilder.withValue(colName, resultSet.getObject(i)); + } catch (ReflectiveOperationException e) { + throw new Sql2oException("Error when trying to set value for column [" + colName + "]", e); } - }; - } - // dot path - long way - // i'm too lazy now to rewrite this case so I just call old unoptimized code... - // TODO: rewrite, get rid of POJO class - return new Setter() { - public void setProperty(Object obj, Object value) { - Pojo pojo = new Pojo(metadata, metadata.isCaseSensitive(), obj); - pojo.setProperty(propertyPath, value, quirks); - } - public Class getType() { - // doesn't used anyway - return Object.class; } - }; - } - - private static class Key { - final String stringKey; - final DefaultResultSetHandlerFactory f; - - DefaultResultSetHandlerFactory factory(){ - return f; - } - - private PojoMetadata getMetadata() { - return f.metadata; - } - - private Quirks getQuirksMode() { - return f.quirks; - } - - private Key(String stringKey, DefaultResultSetHandlerFactory f) { - this.stringKey = stringKey; - this.f = f; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Key key = (Key) o; - - return f.metadata.equals(key.getMetadata()) - && f.quirks == key.getQuirksMode() - && stringKey.equals(key.stringKey); - - } - - @Override - public int hashCode() { - int result = f.metadata.hashCode(); - result = 31 * result + f.quirks.hashCode(); - result = 31 * result + stringKey.hashCode(); - return result; - } - } - - - private static final AbstractCache - c = new AbstractCache() { - @Override - protected ResultSetHandler evaluate(Key key, ResultSetMetaData param) { try { - return key.factory().newResultSetHandler0(param); - } catch (SQLException e) { - throw new RuntimeException(e); + return objectBuilder.build(); + } catch (ReflectiveOperationException e) { + throw new Sql2oException("Error occurred while creating object from ResultSet", e); } - } - }; + }; - @SuppressWarnings("unchecked") - public ResultSetHandler newResultSetHandler(final ResultSetMetaData meta) throws SQLException { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 1; i <= meta.getColumnCount(); i++) { - stringBuilder.append(quirks.getColumnName(meta,i)).append("\n"); - } - return c.get(new Key(stringBuilder.toString(), this),meta); } - @SuppressWarnings("unchecked") - private ResultSetHandler newResultSetHandler0(final ResultSetMetaData meta) throws SQLException { - final Setter[] setters; - final Converter converter; - final boolean useExecuteScalar; - //TODO: it's possible to cache converter/setters/getters - // cache key is ResultSetMetadata + Bean type - - converter = quirks.converterOf(metadata.getType()); - final int columnCount = meta.getColumnCount(); - - setters = new Setter[columnCount + 1]; // setters[0] is always null - for (int i = 1; i <= columnCount; i++) { - String colName = quirks.getColumnName(meta, i); - - setters[i] = getSetter(quirks, colName, metadata); - - // If more than 1 column is fetched (we cannot fall back to executeScalar), - // and the setter doesn't exist, throw exception. - if (this.metadata.throwOnMappingFailure && setters[i] == null && columnCount > 1) { - throw new Sql2oException("Could not map " + colName + " to any property."); - } - } - /** - * Fallback to executeScalar if converter exists, - * we're selecting 1 column, and no property setter exists for the column. - */ - useExecuteScalar = converter != null && columnCount == 1 && setters[1] == null; - return new ResultSetHandler() { - @SuppressWarnings("unchecked") - public T handle(ResultSet resultSet) throws SQLException { - if (useExecuteScalar) { - try { - return (T) converter.convert(quirks.getRSVal(resultSet, 1)); - } catch (ConverterException e) { - throw new Sql2oException("Error occurred while converting value from database to type " + metadata.getType(), e); - } - } - - // otherwise we want executeAndFetch with object mapping - Object pojo = metadata.getObjectConstructor().newInstance(); - for (int colIdx = 1; colIdx <= columnCount; colIdx++) { - Setter setter = setters[colIdx]; - if (setter == null) continue; - setter.setProperty(pojo, quirks.getRSVal(resultSet, colIdx)); - } - - return (T) pojo; - } - }; - } +// @SuppressWarnings("unchecked") +// private ResultSetHandler newResultSetHandler0(final ResultSetMetaData meta) throws SQLException { +// +// return resultSet -> { +// try { +// while (resultSet.next()) { +// for (int i = 1; i <= metadata.getColumnCount(); i++) { +// String colName = quirks.getColumnName(resultSet.getMetaData(), i); +// objectBuilder.withValue(colName, resultSet.getObject(i)); +// } +// } +// return objectBuilder.build(); +// } catch (ReflectiveOperationException e) { +// throw new Sql2oException("Error occurred while creating object from ResultSet", e); +// } +// }; +// } + + +// final Setter[] setters; +// final Converter converter; +// final boolean useExecuteScalar; +// //TODO: it's possible to cache converter/setters/getters +// // cache key is ResultSetMetadata + Bean type +// +// converter = quirks.converterOf(metadata.getType()); +// final int columnCount = meta.getColumnCount(); +// +// setters = new Setter[columnCount + 1]; // setters[0] is always null +// for (int i = 1; i <= columnCount; i++) { +// String colName = quirks.getColumnName(meta, i); +// +// setters[i] = getSetter(quirks, colName, metadata); +// +// // If more than 1 column is fetched (we cannot fall back to executeScalar), +// // and the setter doesn't exist, throw exception. +// if (this.metadata.throwOnMappingFailure && setters[i] == null && columnCount > 1) { +// throw new Sql2oException("Could not map " + colName + " to any property."); +// } +// } +// /** +// * Fallback to executeScalar if converter exists, +// * we're selecting 1 column, and no property setter exists for the column. +// */ +// useExecuteScalar = converter != null && columnCount == 1 && setters[1] == null; +// return resultSet -> { +// if (useExecuteScalar) { +// try { +// return (T) converter.convert(quirks.getRSVal(resultSet, 1)); +// } catch (ConverterException e) { +// throw new Sql2oException("Error occurred while converting value from database to type " + metadata.getType(), e); +// } +// } +// +// // otherwise we want executeAndFetch with object mapping +// Object pojo = metadata.getObjectConstructor().newInstance(); +// for (int colIdx = 1; colIdx <= columnCount; colIdx++) { +// Setter setter = setters[colIdx]; +// if (setter == null) continue; +// setter.setProperty(pojo, quirks.getRSVal(resultSet, colIdx)); +// } +// +// return (T) pojo; +// }; +// } } diff --git a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java index 5f97fd9e..dbcef56d 100644 --- a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java +++ b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java @@ -2,6 +2,9 @@ import org.sql2o.quirks.Quirks; import org.sql2o.reflection.PojoMetadata; +import org.sql2o.reflection2.ObjectBuildable; +import org.sql2o.reflection2.ObjectBuildableFactory; +import org.sql2o.reflection2.ObjectBuildableFactoryDelegate; import java.util.Map; @@ -54,12 +57,15 @@ public void setQuirks(Quirks quirks) { this.quirks = quirks; } - - - @SuppressWarnings("unchecked") public ResultSetHandlerFactory newFactory(Class clazz) { - PojoMetadata pojoMetadata = new PojoMetadata(clazz, caseSensitive, autoDeriveColumnNames, columnMappings, throwOnMappingError); - return new DefaultResultSetHandlerFactory(pojoMetadata, quirks); + + return new DefaultResultSetHandlerFactory<>(() -> { + try { + return ObjectBuildableFactory.forClass(clazz, new Settings(new NamingConvention(caseSensitive), quirks)); + } catch (ReflectiveOperationException e) { + throw new Sql2oException("Error while trying to construct object from class " + clazz, e); + } + }, quirks); } } diff --git a/core/src/main/java/org/sql2o/NamingConvention.java b/core/src/main/java/org/sql2o/NamingConvention.java new file mode 100644 index 00000000..fe6b5002 --- /dev/null +++ b/core/src/main/java/org/sql2o/NamingConvention.java @@ -0,0 +1,17 @@ +package org.sql2o; + +public class NamingConvention { + + private final boolean caseSensitive; + + public NamingConvention(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public String deriveName(String name) { + if (!caseSensitive) + return name.toLowerCase(); + + return name; + } +} diff --git a/core/src/main/java/org/sql2o/Settings.java b/core/src/main/java/org/sql2o/Settings.java new file mode 100644 index 00000000..a4fe954b --- /dev/null +++ b/core/src/main/java/org/sql2o/Settings.java @@ -0,0 +1,22 @@ +package org.sql2o; + +import org.sql2o.quirks.Quirks; + +public class Settings { + + private final Quirks quirks; + private final NamingConvention namingConvention; + + public Settings(NamingConvention namingConvention, Quirks quirks) { + this.quirks = quirks; + this.namingConvention = namingConvention; + } + + public Quirks getQuirks() { + return quirks; + } + + public NamingConvention getNamingConvention() { + return namingConvention; + } +} diff --git a/core/src/main/java/org/sql2o/reflection/FactoryFacade.java b/core/src/main/java/org/sql2o/reflection/FactoryFacade.java index 91d194dd..847be4ef 100644 --- a/core/src/main/java/org/sql2o/reflection/FactoryFacade.java +++ b/core/src/main/java/org/sql2o/reflection/FactoryFacade.java @@ -11,30 +11,8 @@ public class FactoryFacade { MethodGetterFactory mg = new ReflectionMethodGetterFactory(); MethodSetterFactory m = new ReflectionMethodSetterFactory(); ObjectConstructorFactory o = new ReflectionObjectConstructorFactory(); -// try { -// m = (MethodSetterFactory) Class -// .forName("org.sql2o.reflection.MethodAccessorsGenerator") -// .newInstance(); -// mg = (MethodGetterFactory) m; -// o = (ObjectConstructorFactory) m; -// } catch (Throwable ex) { -// mg = new ReflectionMethodGetterFactory(); -// m = new ReflectionMethodSetterFactory(); -// o = null; -// } FieldGetterFactory fg = new ReflectionFieldGetterFactory(); FieldSetterFactory f = new ReflectionFieldSetterFactory(); -// try { -// Class clsg = Class.forName("org.sql2o.reflection.UnsafeFieldGetterFactory"); -// fg = (FieldGetterFactory) clsg.newInstance(); -// Class cls = Class.forName("org.sql2o.reflection.UnsafeFieldSetterFactory"); -// f = (FieldSetterFactory) cls.newInstance(); -// if(o==null) o = (ObjectConstructorFactory) f; -// } catch (Throwable ex) { -// fg = new ReflectionFieldGetterFactory(); -// f = new ReflectionFieldSetterFactory(); -// o = new ReflectionObjectConstructorFactory(); -// } instance = new FactoryFacade(fg, mg, f, m, o); } diff --git a/core/src/main/java/org/sql2o/reflection/Pojo.java b/core/src/main/java/org/sql2o/reflection/Pojo.java index d0d00a8a..0aa08393 100644 --- a/core/src/main/java/org/sql2o/reflection/Pojo.java +++ b/core/src/main/java/org/sql2o/reflection/Pojo.java @@ -52,7 +52,7 @@ public void setProperty(String propertyPath, Object value, Quirks quirks){ setter.setProperty(this.object, subValue); } - PojoMetadata subMetadata = new PojoMetadata(setter.getType(), this.caseSensitive, this.metadata.isAutoDeriveColumnNames(), this.metadata.getColumnMappings(), this.metadata.throwOnMappingFailure); + PojoMetadata subMetadata = new PojoMetadata(setter.getType(), this.caseSensitive, this.metadata.isAutoDeriveColumnNames(), this.metadata.getColumnMappings()); Pojo subPojo = new Pojo(subMetadata, this.caseSensitive, subValue); subPojo.setProperty(newPath, value, quirks); } diff --git a/core/src/main/java/org/sql2o/reflection/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection/PojoMetadata.java index f67c6b88..104dc605 100644 --- a/core/src/main/java/org/sql2o/reflection/PojoMetadata.java +++ b/core/src/main/java/org/sql2o/reflection/PojoMetadata.java @@ -24,10 +24,9 @@ public class PojoMetadata { private final Map columnMappings; private final FactoryFacade factoryFacade = FactoryFacade.getInstance(); - private boolean caseSensitive; - private boolean autoDeriveColumnNames; - public final boolean throwOnMappingFailure; - private Class clazz; + private final boolean caseSensitive; + private final boolean autoDeriveColumnNames; + private final Class clazz; public boolean isCaseSensitive() { return caseSensitive; @@ -59,15 +58,13 @@ public int hashCode() { return result; } - public PojoMetadata(Class clazz, boolean caseSensitive, boolean autoDeriveColumnNames, Map columnMappings, boolean throwOnMappingError) { + public PojoMetadata(Class clazz, boolean caseSensitive, boolean autoDeriveColumnNames, Map columnMappings) { this.caseSensitive = caseSensitive; this.autoDeriveColumnNames = autoDeriveColumnNames; this.clazz = clazz; this.columnMappings = columnMappings == null ? Collections.emptyMap() : columnMappings; this.propertyInfo = getPropertyInfoThroughCache(); - this.throwOnMappingFailure = throwOnMappingError; - } public ObjectConstructor getObjectConstructor() { diff --git a/core/src/main/java/org/sql2o/reflection2/ObjectBuildable.java b/core/src/main/java/org/sql2o/reflection2/ObjectBuildable.java new file mode 100644 index 00000000..94e674a2 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/ObjectBuildable.java @@ -0,0 +1,8 @@ +package org.sql2o.reflection2; + +public interface ObjectBuildable { + + void withValue(String columnName, Object value) throws ReflectiveOperationException; + + T build() throws ReflectiveOperationException; +} diff --git a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java new file mode 100644 index 00000000..cace5042 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java @@ -0,0 +1,17 @@ +package org.sql2o.reflection2; + +import org.sql2o.NamingConvention; +import org.sql2o.Settings; + +public class ObjectBuildableFactory { + + public static ObjectBuildable forClass(Class targetClass, Settings settings) throws ReflectiveOperationException { + + if (targetClass.isRecord()) { + return new RecordBuilder<>(targetClass, settings); + } + + final var pojoMetadata = new PojoMetadata<>(targetClass, settings); + return new PojoBuilder<>(targetClass, settings, pojoMetadata); + } +} diff --git a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactoryDelegate.java b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactoryDelegate.java new file mode 100644 index 00000000..b4bdc675 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactoryDelegate.java @@ -0,0 +1,5 @@ +package org.sql2o.reflection2; + +public interface ObjectBuildableFactoryDelegate { + ObjectBuildable newObjectBuilder(); +} diff --git a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java new file mode 100644 index 00000000..058e3eb4 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java @@ -0,0 +1,27 @@ +package org.sql2o.reflection2; + +import org.sql2o.Settings; + +public class PojoBuilder implements ObjectBuildable { + + private final Settings settings; + private final PojoMetadata pojoMetadata; + private final T pojo; + + public PojoBuilder(Class pojoClass, Settings settings, PojoMetadata pojoMetadata) throws ReflectiveOperationException { + this.settings = settings; + this.pojoMetadata = pojoMetadata; + pojo = pojoMetadata.getConstructor().newInstance(); + } + + @Override + public void withValue(String columnName, Object value) throws ReflectiveOperationException { + final var derivedName = settings.getNamingConvention().deriveName(columnName); + pojoMetadata.getPojoProperty(derivedName).SetProperty(this.pojo, value); + } + + @Override + public T build() throws ReflectiveOperationException { + return pojo; + } +} diff --git a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java new file mode 100644 index 00000000..f71969e4 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java @@ -0,0 +1,130 @@ +package org.sql2o.reflection2; + +import org.sql2o.Settings; + +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +// Helper class for the PojoBuilder class. This class contains cacheable metadata about the POJO class. +public class PojoMetadata { + + private final Constructor constructor; + private final Map pojoProperties; + private final Settings settings; + + public PojoMetadata(Class clazz, Settings settings) throws ReflectiveOperationException { + this.constructor = clazz.getDeclaredConstructor(); + this.constructor.setAccessible(true); + this.settings = settings; + + final var pojoPropertyBuilders = getPojoPropertyBuilders(clazz); + + final var pojoPropertiesStream = pojoPropertyBuilders.stream().map(PojoPropertyBuilder::build); + pojoProperties = + pojoPropertiesStream.collect(Collectors.toMap(PojoProperty::getName, p -> p)); + + // also associate the annotated name with the property + final var pojoPropertiesWithAnnotatedName = pojoProperties.values().stream() + .filter(p -> p.getAnnotatedName() != null).toList(); + pojoPropertiesWithAnnotatedName.forEach(p -> pojoProperties.put(p.getAnnotatedName(), p)); + } + + public Constructor getConstructor() { + return constructor; + } + + public PojoProperty getPojoProperty(String name) { + return pojoProperties.get(name); + } + + private List getPojoPropertyBuilders(Class clazz) { + final var pojoPropertyBuilders = new HashMap(); + initializeForClassRecursive(clazz, pojoPropertyBuilders); + return new ArrayList<>(pojoPropertyBuilders.values()); + } + + /*** + * This method fills the pojoPropertyBuilders parameter with instances of the PojoPropertyBuilder class, for each property in the class and its subclasses. + * @param clazz + * @param pojoPropertyBuilders + */ + private void initializeForClassRecursive(Class clazz, HashMap pojoPropertyBuilders) { + for (final var method : clazz.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + method.setAccessible(true); + final var methodName = method.getName(); + if (methodName.startsWith("get") && method.getParameterCount() == 0) { + final var propertyName = settings.getNamingConvention().deriveName(methodName.substring(3)); + pojoPropertyBuilders.computeIfAbsent(propertyName, name -> new PojoPropertyBuilder(name, settings)).withGetter(method); + } else if (methodName.startsWith("is") && method.getParameterCount() == 0) { + final var propertyName = settings.getNamingConvention().deriveName(methodName.substring(2)); + pojoPropertyBuilders.computeIfAbsent(propertyName, name -> new PojoPropertyBuilder(name, settings)).withGetter(method); + } else if (methodName.startsWith("set") && method.getParameterCount() == 1) { + final var propertyName = settings.getNamingConvention().deriveName(methodName.substring(3)); + pojoPropertyBuilders.computeIfAbsent(propertyName, name -> new PojoPropertyBuilder(name, settings)).withSetter(method); + } + + if (pojoPropertyBuilders.containsKey(methodName)){ + pojoPropertyBuilders.get(methodName).withAnnotatedName(deriveAnnotatedName(method)); + } + } + + for (final var field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + final var fieldName = settings.getNamingConvention().deriveName(field.getName()); + pojoPropertyBuilders.computeIfAbsent(fieldName, name -> new PojoPropertyBuilder(name, settings)).withField(field); + + if (pojoPropertyBuilders.containsKey(fieldName)){ + pojoPropertyBuilders.get(fieldName).withAnnotatedName(deriveAnnotatedName(field)); + } + } + + // Recursively initialize for the superclass, except if the superclass is 'Object'. + final var superClass = clazz.getSuperclass(); + if (!superClass.equals(Object.class)) { + initializeForClassRecursive(superClass, pojoPropertyBuilders); + } + } + + private static String deriveAnnotatedName(Executable method) { + if (!isJavaxPersistenceColumnPresent()) + return null; + + final var columnAnnotation = method.getAnnotation(javax.persistence.Column.class); + if (columnAnnotation == null) return null; + return columnAnnotation.name(); + } + + private static String deriveAnnotatedName(Field field) { + if (!isJavaxPersistenceColumnPresent()) + return null; + + final var columnAnnotation = field.getAnnotation(javax.persistence.Column.class); + if (columnAnnotation == null) return null; + return columnAnnotation.name(); + } + + + private static Boolean javaxPersistenceColumnPresence = null; + private static boolean isJavaxPersistenceColumnPresent() { + if (javaxPersistenceColumnPresence != null) + return javaxPersistenceColumnPresence; + + try { + Class.forName("javax.persistence.Column"); + javaxPersistenceColumnPresence = true; + } catch (ClassNotFoundException e) { + javaxPersistenceColumnPresence = false; + } + + return javaxPersistenceColumnPresence; + } +} diff --git a/core/src/main/java/org/sql2o/reflection2/PojoProperty.java b/core/src/main/java/org/sql2o/reflection2/PojoProperty.java new file mode 100644 index 00000000..b84c1a19 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/PojoProperty.java @@ -0,0 +1,71 @@ +package org.sql2o.reflection2; + +import org.sql2o.Settings; +import org.sql2o.Sql2oException; +import org.sql2o.converters.ConverterException; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class PojoProperty { + + private final String name; + private final String annotatedName; + private final Method getter; + private final Method setter; + private final Field field; + private final Settings settings; + + public PojoProperty(String name, String annotatedName, Method getter, Method setter, Field field, Settings settings) { + this.name = name; + this.annotatedName = annotatedName; + this.getter = getter; + this.setter = setter; + this.field = field; + if (field != null) { + this.field.setAccessible(true); + } + + this.settings = settings; + } + + public String getName() { + return name; + } + + public String getAnnotatedName() { + return annotatedName; + } + + public void SetProperty(Object obj, Object value) throws ReflectiveOperationException { + if (setter != null) { + try { + final var propertyType = setter.getParameters()[0].getType(); + final var convertedValue = settings.getQuirks().converterOf(propertyType).convert(value); + if (convertedValue == null && propertyType.isPrimitive()) { + return; // don't try to set null to a setter to a primitive type. + } + setter.invoke(obj, convertedValue); + } catch (ConverterException ex) { + throw new Sql2oException("Error trying to convert value of type " + value.getClass().getName() + " to property " + name + " [" + setter.getName() + "] of type " + setter.getDeclaringClass(), ex); + } + return; + } + + if(field != null) { + try { + final var propertyType = field.getType(); + final var convertedValue = settings.getQuirks().converterOf(propertyType).convert(value); + if (convertedValue == null && propertyType.isPrimitive()) { + return; // don't try to set null to a field to a primitive type. + } + field.set(obj, convertedValue); + } catch (ConverterException ex) { + throw new Sql2oException("Error trying to convert value of type " + value.getClass().getName() + " to field " + name + " of type " + field.getDeclaringClass(), ex); + } + return; + } + + throw new Sql2oException("No setter or field found for property " + name); + } +} diff --git a/core/src/main/java/org/sql2o/reflection2/PojoPropertyBuilder.java b/core/src/main/java/org/sql2o/reflection2/PojoPropertyBuilder.java new file mode 100644 index 00000000..938bf98a --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/PojoPropertyBuilder.java @@ -0,0 +1,41 @@ +package org.sql2o.reflection2; + +import org.sql2o.Settings; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class PojoPropertyBuilder { + + private Method getter; + private Method setter; + private Field field; + private String annotatedName; + private final String name; + private final Settings settings; + + public PojoPropertyBuilder(String name, Settings settings) { + this.name = name; + this.settings = settings; + } + + public void withGetter(Method getter) { + this.getter = getter; + } + + public void withSetter(Method setter) { + this.setter = setter; + } + + public void withField(Field field) { + this.field = field; + } + + public void withAnnotatedName(String annotatedName) { + this.annotatedName = annotatedName; + } + + public PojoProperty build() { + return new PojoProperty(name, annotatedName, getter, setter, field, settings); + } +} diff --git a/core/src/main/java/org/sql2o/reflection2/RecordBuilder.java b/core/src/main/java/org/sql2o/reflection2/RecordBuilder.java new file mode 100644 index 00000000..e0472eb8 --- /dev/null +++ b/core/src/main/java/org/sql2o/reflection2/RecordBuilder.java @@ -0,0 +1,56 @@ +package org.sql2o.reflection2; + +import org.sql2o.NamingConvention; +import org.sql2o.Settings; +import org.sql2o.Sql2oException; +import org.sql2o.converters.ConverterException; +import org.sql2o.quirks.Quirks; + +import java.lang.reflect.Constructor; +import java.util.LinkedHashMap; + +public class RecordBuilder implements ObjectBuildable{ + + private final Constructor constructor; + private final LinkedHashMap values = new LinkedHashMap<>(); + private final Settings settings; + + @SuppressWarnings("unchecked") + public RecordBuilder(Class recordClass, Settings settings) { + + this.constructor = (Constructor)recordClass.getDeclaredConstructors()[0]; + this.settings = settings; + + for (final var recordComponent : recordClass.getRecordComponents()) { + values.put(settings.getNamingConvention().deriveName(recordComponent.getName()), new RecordValue(recordComponent.getType())); + } + } + + @Override + public void withValue(String columnName, Object value) { + final var derivedName = settings.getNamingConvention().deriveName(columnName); + if (!values.containsKey(derivedName)) { + throw new IllegalArgumentException("No such field in record: " + columnName); + } + final var convVal = values.get(derivedName); + try { + convVal.value = settings.getQuirks().converterOf(convVal.type).convert(value); + } catch (ConverterException e) { + throw new Sql2oException("Error trying to convert column " + columnName + " to type " + convVal.type, e); + } + } + + @Override + public T build() throws ReflectiveOperationException { + return constructor.newInstance(values.values().stream().map(recordValue -> recordValue.value).toArray()); + } + + private static class RecordValue { + public Object value = null; + public final Class type; + + public RecordValue(Class type) { + this.type = type; + } + } +} diff --git a/core/src/test/java/org/sql2o/Sql2oTest.java b/core/src/test/java/org/sql2o/Sql2oTest.java index 61893005..c4e6f777 100644 --- a/core/src/test/java/org/sql2o/Sql2oTest.java +++ b/core/src/test/java/org/sql2o/Sql2oTest.java @@ -48,31 +48,6 @@ public Sql2oTest(DbType dbType, String testName) { super(dbType, testName); } - //@Test TODO. commented out. Can't get test to work without an application server. - public void testCreateSql2oFromJndi() throws Exception { - System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory"); - System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming"); - - InitialContext ic = new InitialContext(); - - ic.createSubcontext("java:"); - ic.createSubcontext("java:comp"); - ic.createSubcontext("java:comp/env"); - - JDBCDataSource datasource = new JDBCDataSource(); - datasource.setUrl(dbType.url); - datasource.setUser(dbType.user); - datasource.setPassword(dbType.pass); - - ic.bind("java:comp/env/Sql2o", datasource); - - System.out.println("Datasource initialized."); - - Sql2o jndiSql2o = new Sql2o("Sql2o"); - - assertTrue(jndiSql2o != null); - } - @Test public void testExecuteAndFetch(){ createAndFillUserTable(); diff --git a/core/src/test/java/org/sql2o/records/RecordsTest.java b/core/src/test/java/org/sql2o/records/RecordsTest.java new file mode 100644 index 00000000..984d8ed7 --- /dev/null +++ b/core/src/test/java/org/sql2o/records/RecordsTest.java @@ -0,0 +1,57 @@ +package org.sql2o.records; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.sql2o.H2ArgumentsSourceProvider; +import org.sql2o.Sql2o; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RecordsTest { + + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(H2ArgumentsSourceProvider.class) + void deserializing_a_record_with_constructor_should_work(String dbName, String url, String user, String pass) { + // Arrange + final var sql2o = new Sql2o(url, user, pass); + createATableWithSomeData(sql2o); + + // Act + List recordResult; + try (final var con = sql2o.open()) { + recordResult = con.createQuery("SELECT id, name, is_ok as \"isOk\" FROM record_entity") + .executeAndFetch(RecordEntity.class); + } + + // Assert + assertEquals(2, recordResult.size()); + final var firstRecord = recordResult.get(0); + assertEquals(1, firstRecord.id()); + assertEquals("name1", firstRecord.name()); + assertTrue(firstRecord.isOk()); + } + + private void createATableWithSomeData(Sql2o sql2o){ + try (var con = sql2o.open()) { + con.createQuery("CREATE TABLE record_entity (id INT PRIMARY KEY, name VARCHAR(255), is_ok BOOLEAN)") + .executeUpdate(); + con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") + .addParameter("id", 1) + .addParameter("name", "name1") + .addParameter("is_ok", true) + .executeUpdate(); + con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") + .addParameter("id", 2) + .addParameter("name", "name2") + .addParameter("is_ok", false) + .executeUpdate(); + } + + } + + public record RecordEntity(int id, String name, boolean isOk) {} +} diff --git a/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java b/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java index df0cb15a..62b95ac3 100644 --- a/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java +++ b/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java @@ -51,7 +51,7 @@ public void testUppercaseAnnotationFieldPojo() { } private PojoMetadata newPojoMetadata(Class clazz) { - return new PojoMetadata(clazz, false, false, ImmutableMap. of(), true); + return new PojoMetadata(clazz, false, false, ImmutableMap. of()); } private static class NoAnnotation { diff --git a/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java b/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java index b5afb7f0..6b43500b 100644 --- a/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java +++ b/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java @@ -203,6 +203,24 @@ public void testUUID() { } + @Test + public void testArray() { + try (Connection con = sql2o.open()) { + con.createQuery("create table arraytest (id serial primary key, data text[])").executeUpdate(); + + final var data = new String[]{"foo", "bar", "baz"}; + + con.createQuery("insert into arraytest(data) values (:data)") + .addParameter("data", data) + .executeUpdate(); + + var fetchedData = con.createQuery("select data from arraytest") + .executeScalar(String[].class); + + assertThat(fetchedData, is(equalTo(data))); + } + } + } diff --git a/pom.xml b/pom.xml index f33589fc..2ab95a4b 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,8 @@ 4.0.2.RELEASE UTF-8 + 1.8 + 1.8 From 0e94f1bf986b04252a3877cba2c120c5b8f3f6e2 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Mon, 19 Aug 2024 07:06:58 +0200 Subject: [PATCH 02/10] Fixed a couple of failing tests related to the ongoing reflection rewriting --- ...DefaultResultSetHandlerFactoryBuilder.java | 9 ++++++- .../main/java/org/sql2o/NamingConvention.java | 15 ++++++++--- core/src/main/java/org/sql2o/Settings.java | 8 +++++- .../org/sql2o/reflection/PojoMetadata.java | 6 ++--- .../reflection2/ObjectBuildableFactory.java | 7 ++--- .../org/sql2o/reflection2/PojoBuilder.java | 11 +++++++- .../org/sql2o/reflection2/PojoMetadata.java | 13 +++++++-- ...ToCamelCase.java => SnakeToCamelCase.java} | 2 +- .../sql2o/tools/SnakeToCamelCaseTests.java | 27 +++++++++++++++++++ .../tools/UnderscoreToCamelCaseTests.java | 27 ------------------- 10 files changed, 83 insertions(+), 42 deletions(-) rename core/src/main/java/org/sql2o/tools/{UnderscoreToCamelCase.java => SnakeToCamelCase.java} (96%) create mode 100644 core/src/test/java/org/sql2o/tools/SnakeToCamelCaseTests.java delete mode 100644 core/src/test/java/org/sql2o/tools/UnderscoreToCamelCaseTests.java diff --git a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java index dbcef56d..e62d91fd 100644 --- a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java +++ b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java @@ -61,7 +61,14 @@ public ResultSetHandlerFactory newFactory(Class clazz) { return new DefaultResultSetHandlerFactory<>(() -> { try { - return ObjectBuildableFactory.forClass(clazz, new Settings(new NamingConvention(caseSensitive), quirks)); + return ObjectBuildableFactory.forClass( + clazz, + new Settings( + new NamingConvention(caseSensitive, autoDeriveColumnNames), + quirks, + throwOnMappingError), + getColumnMappings() + ); } catch (ReflectiveOperationException e) { throw new Sql2oException("Error while trying to construct object from class " + clazz, e); } diff --git a/core/src/main/java/org/sql2o/NamingConvention.java b/core/src/main/java/org/sql2o/NamingConvention.java index fe6b5002..359e8013 100644 --- a/core/src/main/java/org/sql2o/NamingConvention.java +++ b/core/src/main/java/org/sql2o/NamingConvention.java @@ -1,17 +1,26 @@ package org.sql2o; +import org.sql2o.tools.SnakeToCamelCase; + public class NamingConvention { private final boolean caseSensitive; + private final boolean autoDeriveColumnNames; - public NamingConvention(boolean caseSensitive) { + public NamingConvention(boolean caseSensitive, boolean autoDeriveColumnNames) { this.caseSensitive = caseSensitive; + this.autoDeriveColumnNames = autoDeriveColumnNames; } public String deriveName(String name) { + var derivedName = name; + + if (autoDeriveColumnNames) { + derivedName = SnakeToCamelCase.convert(derivedName); + } if (!caseSensitive) - return name.toLowerCase(); + derivedName = derivedName.toLowerCase(); - return name; + return derivedName; } } diff --git a/core/src/main/java/org/sql2o/Settings.java b/core/src/main/java/org/sql2o/Settings.java index a4fe954b..278ba02b 100644 --- a/core/src/main/java/org/sql2o/Settings.java +++ b/core/src/main/java/org/sql2o/Settings.java @@ -6,10 +6,12 @@ public class Settings { private final Quirks quirks; private final NamingConvention namingConvention; + private final boolean throwOnMappingError; - public Settings(NamingConvention namingConvention, Quirks quirks) { + public Settings(NamingConvention namingConvention, Quirks quirks, boolean throwOnMappingError) { this.quirks = quirks; this.namingConvention = namingConvention; + this.throwOnMappingError = throwOnMappingError; } public Quirks getQuirks() { @@ -19,4 +21,8 @@ public Quirks getQuirks() { public NamingConvention getNamingConvention() { return namingConvention; } + + public boolean isThrowOnMappingError(){ + return throwOnMappingError; + } } diff --git a/core/src/main/java/org/sql2o/reflection/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection/PojoMetadata.java index 104dc605..bd31fdd6 100644 --- a/core/src/main/java/org/sql2o/reflection/PojoMetadata.java +++ b/core/src/main/java/org/sql2o/reflection/PojoMetadata.java @@ -2,7 +2,7 @@ import org.sql2o.Sql2oException; import org.sql2o.tools.AbstractCache; -import org.sql2o.tools.UnderscoreToCamelCase; +import org.sql2o.tools.SnakeToCamelCase; import javax.persistence.Column; import java.lang.reflect.AnnotatedElement; @@ -173,7 +173,7 @@ public Getter getPropertyGetterIfExists(String propertyName) { } if (autoDeriveColumnNames) { - name = UnderscoreToCamelCase.convert(name); + name = SnakeToCamelCase.convert(name); if (!this.caseSensitive) name = name.toLowerCase(); } @@ -204,7 +204,7 @@ public Setter getPropertySetterIfExists(String propertyName) { } if (autoDeriveColumnNames) { - name = UnderscoreToCamelCase.convert(name); + name = SnakeToCamelCase.convert(name); if (!this.caseSensitive) name = name.toLowerCase(); } diff --git a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java index cace5042..469f1b0d 100644 --- a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java +++ b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java @@ -1,17 +1,18 @@ package org.sql2o.reflection2; -import org.sql2o.NamingConvention; import org.sql2o.Settings; +import java.util.Map; + public class ObjectBuildableFactory { - public static ObjectBuildable forClass(Class targetClass, Settings settings) throws ReflectiveOperationException { + public static ObjectBuildable forClass(Class targetClass, Settings settings, Map columnMappings) throws ReflectiveOperationException { if (targetClass.isRecord()) { return new RecordBuilder<>(targetClass, settings); } - final var pojoMetadata = new PojoMetadata<>(targetClass, settings); + final var pojoMetadata = new PojoMetadata<>(targetClass, settings, columnMappings); return new PojoBuilder<>(targetClass, settings, pojoMetadata); } } diff --git a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java index 058e3eb4..41cb5b11 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java @@ -1,6 +1,7 @@ package org.sql2o.reflection2; import org.sql2o.Settings; +import org.sql2o.Sql2oException; public class PojoBuilder implements ObjectBuildable { @@ -17,7 +18,15 @@ public PojoBuilder(Class pojoClass, Settings settings, PojoMetadata pojoMe @Override public void withValue(String columnName, Object value) throws ReflectiveOperationException { final var derivedName = settings.getNamingConvention().deriveName(columnName); - pojoMetadata.getPojoProperty(derivedName).SetProperty(this.pojo, value); + final var pojoProperty = pojoMetadata.getPojoProperty(derivedName); + + if (pojoProperty == null) { + if (settings.isThrowOnMappingError()){ + throw new Sql2oException("Could not map " + columnName + " to any property."); + } + return; + } + pojoProperty.SetProperty(this.pojo, value); } @Override diff --git a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java index f71969e4..da2eb9c6 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java @@ -1,6 +1,7 @@ package org.sql2o.reflection2; import org.sql2o.Settings; +import org.sql2o.Sql2oException; import java.lang.reflect.*; import java.util.ArrayList; @@ -15,11 +16,13 @@ public class PojoMetadata { private final Constructor constructor; private final Map pojoProperties; private final Settings settings; + private final Map columnMappings; - public PojoMetadata(Class clazz, Settings settings) throws ReflectiveOperationException { + public PojoMetadata(Class clazz, Settings settings, Map columnMappings) throws ReflectiveOperationException { this.constructor = clazz.getDeclaredConstructor(); this.constructor.setAccessible(true); this.settings = settings; + this.columnMappings = columnMappings; final var pojoPropertyBuilders = getPojoPropertyBuilders(clazz); @@ -38,7 +41,13 @@ public Constructor getConstructor() { } public PojoProperty getPojoProperty(String name) { - return pojoProperties.get(name); + if (columnMappings.containsKey(name)) { + final var columnName = columnMappings.get(name); + if (pojoProperties.containsKey(columnName)) + return pojoProperties.get(columnName); + } + + return pojoProperties.getOrDefault(name, null); } private List getPojoPropertyBuilders(Class clazz) { diff --git a/core/src/main/java/org/sql2o/tools/UnderscoreToCamelCase.java b/core/src/main/java/org/sql2o/tools/SnakeToCamelCase.java similarity index 96% rename from core/src/main/java/org/sql2o/tools/UnderscoreToCamelCase.java rename to core/src/main/java/org/sql2o/tools/SnakeToCamelCase.java index 76777af6..33afd4d3 100644 --- a/core/src/main/java/org/sql2o/tools/UnderscoreToCamelCase.java +++ b/core/src/main/java/org/sql2o/tools/SnakeToCamelCase.java @@ -6,7 +6,7 @@ * @author ryancarlson * @author dimzon - complete rewrite */ -public class UnderscoreToCamelCase { +public class SnakeToCamelCase { public static String convert(String underscore){ if(underscore==null || underscore.isEmpty()) return underscore; return convert00(underscore); diff --git a/core/src/test/java/org/sql2o/tools/SnakeToCamelCaseTests.java b/core/src/test/java/org/sql2o/tools/SnakeToCamelCaseTests.java new file mode 100644 index 00000000..fd44eb6f --- /dev/null +++ b/core/src/test/java/org/sql2o/tools/SnakeToCamelCaseTests.java @@ -0,0 +1,27 @@ +package org.sql2o.tools; + +import junit.framework.TestCase; + +public class SnakeToCamelCaseTests extends TestCase { + + public void testBasicConversions() { + assertEquals("myStringVariable", SnakeToCamelCase.convert("my_string_variable")); + assertEquals("string", SnakeToCamelCase.convert("string")); + assertEquals("myReallyLongStringVariableName", SnakeToCamelCase.convert("my_really_long_string_variable_name")); + assertEquals("myString2WithNumbers4", SnakeToCamelCase.convert("my_string2_with_numbers_4")); + assertEquals("myStringWithMixedCase", SnakeToCamelCase.convert("my_string_with_MixED_CaSe")); + } + + public void testNullString() { + assertNull(SnakeToCamelCase.convert(null)); + } + + public void testEmptyStrings() { + assertEquals("", SnakeToCamelCase.convert("")); + assertEquals(" ", SnakeToCamelCase.convert(" ")); + } + public void testWhitespace() { + assertEquals("\t", SnakeToCamelCase.convert("\t")); + assertEquals("\n\n", SnakeToCamelCase.convert("\n\n")); + } +} diff --git a/core/src/test/java/org/sql2o/tools/UnderscoreToCamelCaseTests.java b/core/src/test/java/org/sql2o/tools/UnderscoreToCamelCaseTests.java deleted file mode 100644 index 3675c4f4..00000000 --- a/core/src/test/java/org/sql2o/tools/UnderscoreToCamelCaseTests.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sql2o.tools; - -import junit.framework.TestCase; - -public class UnderscoreToCamelCaseTests extends TestCase { - - public void testBasicConversions() { - assertEquals("myStringVariable", UnderscoreToCamelCase.convert("my_string_variable")); - assertEquals("string", UnderscoreToCamelCase.convert("string")); - assertEquals("myReallyLongStringVariableName", UnderscoreToCamelCase.convert("my_really_long_string_variable_name")); - assertEquals("myString2WithNumbers4", UnderscoreToCamelCase.convert("my_string2_with_numbers_4")); - assertEquals("myStringWithMixedCase", UnderscoreToCamelCase.convert("my_string_with_MixED_CaSe")); - } - - public void testNullString() { - assertNull(UnderscoreToCamelCase.convert(null)); - } - - public void testEmptyStrings() { - assertEquals("", UnderscoreToCamelCase.convert("")); - assertEquals(" ", UnderscoreToCamelCase.convert(" ")); - } - public void testWhitespace() { - assertEquals("\t", UnderscoreToCamelCase.convert("\t")); - assertEquals("\n\n", UnderscoreToCamelCase.convert("\n\n")); - } -} From 3c56b0ef7dd4fffeb798d2f40f8d68ab4880a460 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Fri, 30 Aug 2024 06:28:42 +0200 Subject: [PATCH 03/10] Added support for complex types to the new reflection logic --- .../sql2o/converters/DefaultConverter.java | 8 ++++ .../main/java/org/sql2o/quirks/NoQuirks.java | 15 ++++++- .../reflection2/ObjectBuildableFactory.java | 3 +- .../org/sql2o/reflection2/PojoBuilder.java | 37 ++++++++++++++--- .../org/sql2o/reflection2/PojoMetadata.java | 4 ++ .../org/sql2o/reflection2/PojoProperty.java | 41 +++++++++++++++++++ core/src/test/java/org/sql2o/Sql2oTest.java | 10 +++-- 7 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/org/sql2o/converters/DefaultConverter.java diff --git a/core/src/main/java/org/sql2o/converters/DefaultConverter.java b/core/src/main/java/org/sql2o/converters/DefaultConverter.java new file mode 100644 index 00000000..ae8722a2 --- /dev/null +++ b/core/src/main/java/org/sql2o/converters/DefaultConverter.java @@ -0,0 +1,8 @@ +package org.sql2o.converters; + +public class DefaultConverter extends ConverterBase { + @Override + public Object convert(Object val) throws ConverterException { + return val; + } +} diff --git a/core/src/main/java/org/sql2o/quirks/NoQuirks.java b/core/src/main/java/org/sql2o/quirks/NoQuirks.java index c2678075..dd7cd467 100644 --- a/core/src/main/java/org/sql2o/quirks/NoQuirks.java +++ b/core/src/main/java/org/sql2o/quirks/NoQuirks.java @@ -2,6 +2,7 @@ import org.sql2o.converters.Convert; import org.sql2o.converters.Converter; +import org.sql2o.converters.DefaultConverter; import org.sql2o.quirks.parameterparsing.SqlParameterParsingStrategy; import org.sql2o.quirks.parameterparsing.impl.DefaultSqlParameterParsingStrategy; @@ -35,9 +36,19 @@ public NoQuirks() { public Converter converterOf(Class ofClass) { // if nobody change this collection outside constructor // it's thread-safe - Converter c = converters.get(ofClass); + var c = converters.get(ofClass); + // if no "local" converter let's look in global - return c!=null?c:Convert.getConverterIfExists(ofClass); + if (c == null) { + c = Convert.getConverterIfExists(ofClass); + } + + // Fall back to the default converter + if (c == null) { + c = new DefaultConverter(); + } + + return c; } diff --git a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java index 469f1b0d..8118b106 100644 --- a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java +++ b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java @@ -12,7 +12,8 @@ public static ObjectBuildable forClass(Class targetClass, Settings sett return new RecordBuilder<>(targetClass, settings); } + // todo cache PojoMetadata final var pojoMetadata = new PojoMetadata<>(targetClass, settings, columnMappings); - return new PojoBuilder<>(targetClass, settings, pojoMetadata); + return new PojoBuilder<>(settings, pojoMetadata); } } diff --git a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java index 41cb5b11..acc65c58 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java @@ -9,15 +9,42 @@ public class PojoBuilder implements ObjectBuildable { private final PojoMetadata pojoMetadata; private final T pojo; - public PojoBuilder(Class pojoClass, Settings settings, PojoMetadata pojoMetadata) throws ReflectiveOperationException { + public PojoBuilder(Settings settings, PojoMetadata pojoMetadata) throws ReflectiveOperationException { + this(settings, pojoMetadata, pojoMetadata.getConstructor().newInstance()); + } + + public PojoBuilder(Settings settings, PojoMetadata pojoMetadata, T pojo) { this.settings = settings; this.pojoMetadata = pojoMetadata; - pojo = pojoMetadata.getConstructor().newInstance(); + this.pojo = pojo; } @Override - public void withValue(String columnName, Object value) throws ReflectiveOperationException { - final var derivedName = settings.getNamingConvention().deriveName(columnName); + public void withValue(String columnName, Object obj) throws ReflectiveOperationException { + + final var dotIdx = columnName.indexOf('.'); + String derivedName = null; + if (dotIdx > 0) { + final var subName = columnName.substring(0, dotIdx); + derivedName = settings.getNamingConvention().deriveName(subName); + final var subProperty = pojoMetadata.getPojoProperty(derivedName); + final var newPath = columnName.substring(dotIdx + 1); + + var subObj = subProperty.getValue(this.pojo); + if (subObj == null) { + subObj = subProperty.initializeWithNewInstance(this.pojo); + subProperty.SetProperty(this.pojo, subObj); + } + + final var subPojoMetadata = new PojoMetadata<>(subObj.getClass(), settings, pojoMetadata.getColumnMappings()); + final var subObjectBuilder = new PojoBuilder(settings, subPojoMetadata, subObj); + subObjectBuilder.withValue(newPath, obj); + obj = subObjectBuilder.build(); + } + + if (derivedName == null) { + derivedName = settings.getNamingConvention().deriveName(columnName); + } final var pojoProperty = pojoMetadata.getPojoProperty(derivedName); if (pojoProperty == null) { @@ -26,7 +53,7 @@ public void withValue(String columnName, Object value) throws ReflectiveOperatio } return; } - pojoProperty.SetProperty(this.pojo, value); + pojoProperty.SetProperty(this.pojo, obj); } @Override diff --git a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java index da2eb9c6..54408086 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java @@ -56,6 +56,10 @@ private List getPojoPropertyBuilders(Class clazz) { return new ArrayList<>(pojoPropertyBuilders.values()); } + public Map getColumnMappings() { + return columnMappings; + } + /*** * This method fills the pojoPropertyBuilders parameter with instances of the PojoPropertyBuilder class, for each property in the class and its subclasses. * @param clazz diff --git a/core/src/main/java/org/sql2o/reflection2/PojoProperty.java b/core/src/main/java/org/sql2o/reflection2/PojoProperty.java index b84c1a19..804c69e8 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoProperty.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoProperty.java @@ -1,6 +1,7 @@ package org.sql2o.reflection2; import org.sql2o.Settings; +import org.sql2o.Sql2o; import org.sql2o.Sql2oException; import org.sql2o.converters.ConverterException; @@ -68,4 +69,44 @@ public void SetProperty(Object obj, Object value) throws ReflectiveOperationExce throw new Sql2oException("No setter or field found for property " + name); } + + public Class getType() { + if (setter != null) { + return setter.getParameters()[0].getType(); + } else if (field != null) { + return field.getType(); + } + + throw new Sql2oException("Unexpected error. Could not get type of property " + getName()); + } + + // only used when setting complex types + public Object getValue(Object obj) throws ReflectiveOperationException { + if (getter != null) { + return getter.invoke(obj); + } else if (field != null) { + return field.get(obj); + } + + throw new Sql2oException("No getter or field found for property " + name); + } + + // only used when setting complex types + public Object initializeWithNewInstance(Object obj) throws ReflectiveOperationException { + if (setter != null) { + final var propertyType = setter.getParameters()[0].getType(); + // create new instance. Assume empty constructor. + final var instance = propertyType.getDeclaredConstructor().newInstance(); + setter.invoke(obj, instance); + return instance; + } else if (field != null) { + final var propertyType = field.getType(); + // create new instance. Assume empty constructor. + final var instance = propertyType.getDeclaredConstructor().newInstance(); + field.set(obj, instance); + return instance; + } + + throw new Sql2oException("Could not initialize property " + getName() + " no setter or field found."); + } } diff --git a/core/src/test/java/org/sql2o/Sql2oTest.java b/core/src/test/java/org/sql2o/Sql2oTest.java index c4e6f777..10ef478f 100644 --- a/core/src/test/java/org/sql2o/Sql2oTest.java +++ b/core/src/test/java/org/sql2o/Sql2oTest.java @@ -609,11 +609,13 @@ public void testSuperPojo(){ @Test public void testComplexTypes(){ - ComplexEntity pojo = sql2o.createQuery("select 1 id, 1 \"entity.id\", 'something' \"entity.value\" from (values(0))").setName("testComplexTypes").executeAndFetchFirst(ComplexEntity.class); + try (final var connection = sql2o.open()) { + ComplexEntity pojo = connection.createQuery("select 1 id, 1 \"entity.id\", 'something' \"entity.value\" from (values(0))").setName("testComplexTypes").executeAndFetchFirst(ComplexEntity.class); - assertEquals(1, pojo.id); - assertEquals(1, pojo.getEntity().getId()); - assertEquals("something1", pojo.getEntity().getValue()); + assertEquals(1, pojo.id); + assertEquals(1, pojo.getEntity().getId()); + assertEquals("something1", pojo.getEntity().getValue()); + } } @Test From 597ef8b31a9cccf27800901f72cad64bc2bea4d1 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Tue, 17 Sep 2024 17:03:49 +0200 Subject: [PATCH 04/10] Caching PojoMetadata and some accompanying refactoring --- core/pom.xml | 4 +- .../sql2o/DefaultResultSetHandlerFactory.java | 10 ---- ...DefaultResultSetHandlerFactoryBuilder.java | 3 -- .../main/java/org/sql2o/NamingConvention.java | 2 + .../reflection2/ObjectBuildableFactory.java | 11 ++-- .../org/sql2o/reflection2/PojoBuilder.java | 18 ++++--- .../org/sql2o/reflection2/PojoMetadata.java | 11 +--- core/src/main/java/org/sql2o/tools/Cache.java | 50 +++++++++++++++++++ extensions/db2/pom.xml | 4 +- extensions/oracle-joda-time/pom.xml | 12 +++-- .../java/org/sql2o/issues/OracleTest.java | 7 --- extensions/oracle/pom.xml | 4 +- extensions/pom.xml | 2 +- extensions/postgres/pom.xml | 4 +- .../extensions/postgres/PostgresTest.java | 34 ++++++------- pom.xml | 2 +- 16 files changed, 109 insertions(+), 69 deletions(-) create mode 100644 core/src/main/java/org/sql2o/tools/Cache.java diff --git a/core/pom.xml b/core/pom.xml index c459a2cd..06e87169 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -13,7 +13,7 @@ org.sql2o sql2o-parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT sql2o jar @@ -66,7 +66,7 @@ org.junit.jupiter junit-jupiter - 5.10.2 + 5.11.0 test diff --git a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java index 1c37a082..f891715f 100644 --- a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java +++ b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java @@ -1,19 +1,9 @@ package org.sql2o; -import org.sql2o.converters.Converter; -import org.sql2o.converters.ConverterException; import org.sql2o.quirks.Quirks; -import org.sql2o.reflection.Pojo; -import org.sql2o.reflection.PojoMetadata; -import org.sql2o.reflection.Setter; -import org.sql2o.reflection2.ObjectBuildable; -import org.sql2o.reflection2.ObjectBuildableFactory; import org.sql2o.reflection2.ObjectBuildableFactoryDelegate; -import org.sql2o.tools.AbstractCache; -import java.sql.ResultSet; import java.sql.ResultSetMetaData; -import java.sql.SQLException; public class DefaultResultSetHandlerFactory implements ResultSetHandlerFactory { diff --git a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java index e62d91fd..ac9fc602 100644 --- a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java +++ b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactoryBuilder.java @@ -1,10 +1,7 @@ package org.sql2o; import org.sql2o.quirks.Quirks; -import org.sql2o.reflection.PojoMetadata; -import org.sql2o.reflection2.ObjectBuildable; import org.sql2o.reflection2.ObjectBuildableFactory; -import org.sql2o.reflection2.ObjectBuildableFactoryDelegate; import java.util.Map; diff --git a/core/src/main/java/org/sql2o/NamingConvention.java b/core/src/main/java/org/sql2o/NamingConvention.java index 359e8013..31f216d5 100644 --- a/core/src/main/java/org/sql2o/NamingConvention.java +++ b/core/src/main/java/org/sql2o/NamingConvention.java @@ -2,6 +2,8 @@ import org.sql2o.tools.SnakeToCamelCase; +import java.util.Map; + public class NamingConvention { private final boolean caseSensitive; diff --git a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java index 8118b106..caf14705 100644 --- a/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java +++ b/core/src/main/java/org/sql2o/reflection2/ObjectBuildableFactory.java @@ -1,19 +1,24 @@ package org.sql2o.reflection2; import org.sql2o.Settings; +import org.sql2o.tools.Cache; import java.util.Map; public class ObjectBuildableFactory { + private static final Cache, PojoMetadata> pojoMetadataCache = new Cache<>(); + public static ObjectBuildable forClass(Class targetClass, Settings settings, Map columnMappings) throws ReflectiveOperationException { if (targetClass.isRecord()) { return new RecordBuilder<>(targetClass, settings); } - // todo cache PojoMetadata - final var pojoMetadata = new PojoMetadata<>(targetClass, settings, columnMappings); - return new PojoBuilder<>(settings, pojoMetadata); + //noinspection unchecked + final var pojoMetadata = (PojoMetadata) pojoMetadataCache.get(targetClass, () -> + new PojoMetadata<>(targetClass, settings)); + + return new PojoBuilder<>(settings, pojoMetadata, columnMappings); } } diff --git a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java index acc65c58..e2e41a41 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoBuilder.java @@ -3,19 +3,23 @@ import org.sql2o.Settings; import org.sql2o.Sql2oException; +import java.util.Map; + public class PojoBuilder implements ObjectBuildable { private final Settings settings; private final PojoMetadata pojoMetadata; private final T pojo; + private final Map columnMappings; - public PojoBuilder(Settings settings, PojoMetadata pojoMetadata) throws ReflectiveOperationException { - this(settings, pojoMetadata, pojoMetadata.getConstructor().newInstance()); + public PojoBuilder(Settings settings, PojoMetadata pojoMetadata, Map columnMappings) throws ReflectiveOperationException { + this(settings, pojoMetadata, columnMappings, pojoMetadata.getConstructor().newInstance()); } - public PojoBuilder(Settings settings, PojoMetadata pojoMetadata, T pojo) { + public PojoBuilder(Settings settings, PojoMetadata pojoMetadata, Map columnMappings, T pojo) { this.settings = settings; this.pojoMetadata = pojoMetadata; + this.columnMappings = columnMappings; this.pojo = pojo; } @@ -27,7 +31,7 @@ public void withValue(String columnName, Object obj) throws ReflectiveOperationE if (dotIdx > 0) { final var subName = columnName.substring(0, dotIdx); derivedName = settings.getNamingConvention().deriveName(subName); - final var subProperty = pojoMetadata.getPojoProperty(derivedName); + final var subProperty = pojoMetadata.getPojoProperty(derivedName, columnMappings); final var newPath = columnName.substring(dotIdx + 1); var subObj = subProperty.getValue(this.pojo); @@ -36,8 +40,8 @@ public void withValue(String columnName, Object obj) throws ReflectiveOperationE subProperty.SetProperty(this.pojo, subObj); } - final var subPojoMetadata = new PojoMetadata<>(subObj.getClass(), settings, pojoMetadata.getColumnMappings()); - final var subObjectBuilder = new PojoBuilder(settings, subPojoMetadata, subObj); + final var subPojoMetadata = new PojoMetadata<>(subObj.getClass(), settings); + final var subObjectBuilder = new PojoBuilder(settings, subPojoMetadata, columnMappings, subObj); subObjectBuilder.withValue(newPath, obj); obj = subObjectBuilder.build(); } @@ -45,7 +49,7 @@ public void withValue(String columnName, Object obj) throws ReflectiveOperationE if (derivedName == null) { derivedName = settings.getNamingConvention().deriveName(columnName); } - final var pojoProperty = pojoMetadata.getPojoProperty(derivedName); + final var pojoProperty = pojoMetadata.getPojoProperty(derivedName, columnMappings); if (pojoProperty == null) { if (settings.isThrowOnMappingError()){ diff --git a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java index 54408086..894551f1 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoMetadata.java @@ -1,7 +1,6 @@ package org.sql2o.reflection2; import org.sql2o.Settings; -import org.sql2o.Sql2oException; import java.lang.reflect.*; import java.util.ArrayList; @@ -16,13 +15,11 @@ public class PojoMetadata { private final Constructor constructor; private final Map pojoProperties; private final Settings settings; - private final Map columnMappings; - public PojoMetadata(Class clazz, Settings settings, Map columnMappings) throws ReflectiveOperationException { + public PojoMetadata(Class clazz, Settings settings) throws ReflectiveOperationException { this.constructor = clazz.getDeclaredConstructor(); this.constructor.setAccessible(true); this.settings = settings; - this.columnMappings = columnMappings; final var pojoPropertyBuilders = getPojoPropertyBuilders(clazz); @@ -40,7 +37,7 @@ public Constructor getConstructor() { return constructor; } - public PojoProperty getPojoProperty(String name) { + public PojoProperty getPojoProperty(String name, Map columnMappings) { if (columnMappings.containsKey(name)) { final var columnName = columnMappings.get(name); if (pojoProperties.containsKey(columnName)) @@ -56,10 +53,6 @@ private List getPojoPropertyBuilders(Class clazz) { return new ArrayList<>(pojoPropertyBuilders.values()); } - public Map getColumnMappings() { - return columnMappings; - } - /*** * This method fills the pojoPropertyBuilders parameter with instances of the PojoPropertyBuilder class, for each property in the class and its subclasses. * @param clazz diff --git a/core/src/main/java/org/sql2o/tools/Cache.java b/core/src/main/java/org/sql2o/tools/Cache.java new file mode 100644 index 00000000..27e6f572 --- /dev/null +++ b/core/src/main/java/org/sql2o/tools/Cache.java @@ -0,0 +1,50 @@ +package org.sql2o.tools; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class Cache { + private final Map cacheMap; + private final ReadWriteLock lock; + + public Cache() { + cacheMap = new HashMap<>(); + lock = new ReentrantReadWriteLock(); + } + + public Value get(Key key, Callable valueDelegate) { + Value value; + + // Check readlock first + lock.readLock().lock(); + try { + value = cacheMap.get(key); + if (value != null) { + return value; + } + } finally { + lock.readLock().unlock(); + } + + // If not in cache, create a write lock, create and cache the object, and return it. + lock.writeLock().lock(); + try { + // Recheck state because another thread might have acquired write lock and changed state before we did + value = cacheMap.get(key); + if (value == null) { + value = valueDelegate.call(); + cacheMap.put(key, value); + } + + return value; + + } catch (Exception e) { + throw new RuntimeException("Error while getting value from cache", e); + } finally { + lock.writeLock().unlock(); + } + } +} diff --git a/extensions/db2/pom.xml b/extensions/db2/pom.xml index f477ff80..23c99f81 100644 --- a/extensions/db2/pom.xml +++ b/extensions/db2/pom.xml @@ -18,7 +18,7 @@ org.sql2o.extensions extensions-parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT @@ -33,4 +33,4 @@ - \ No newline at end of file + diff --git a/extensions/oracle-joda-time/pom.xml b/extensions/oracle-joda-time/pom.xml index fe4121a5..2d73180c 100644 --- a/extensions/oracle-joda-time/pom.xml +++ b/extensions/oracle-joda-time/pom.xml @@ -18,7 +18,7 @@ org.sql2o.extensions extensions-parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT @@ -40,10 +40,16 @@ org.sql2o.extensions sql2o-oracle - 1.8.0-SNAPSHOT + ${project.parent.version} + test + + + org.sql2o.extensions + sql2o-oracle + 1.9.0-SNAPSHOT test - \ No newline at end of file + diff --git a/extensions/oracle-joda-time/src/test/java/org/sql2o/issues/OracleTest.java b/extensions/oracle-joda-time/src/test/java/org/sql2o/issues/OracleTest.java index 2d15d7f9..fadc018a 100644 --- a/extensions/oracle-joda-time/src/test/java/org/sql2o/issues/OracleTest.java +++ b/extensions/oracle-joda-time/src/test/java/org/sql2o/issues/OracleTest.java @@ -12,24 +12,17 @@ import org.joda.time.DateTime; import org.joda.time.LocalDate; -import org.junit.Ignore; import org.junit.Test; -import org.sql2o.Connection; -import org.sql2o.Query; import org.sql2o.Sql2o; -import org.sql2o.Sql2oException; import org.sql2o.quirks.OracleQuirks; import java.sql.Driver; import java.sql.DriverManager; import java.util.Date; -import java.util.UUID; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; /** * Created with IntelliJ IDEA. diff --git a/extensions/oracle/pom.xml b/extensions/oracle/pom.xml index 8b72a5cf..c37b734a 100644 --- a/extensions/oracle/pom.xml +++ b/extensions/oracle/pom.xml @@ -18,7 +18,7 @@ org.sql2o.extensions extensions-parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT @@ -44,4 +44,4 @@ - \ No newline at end of file + diff --git a/extensions/pom.xml b/extensions/pom.xml index 7b354ebf..d190b8cc 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -18,7 +18,7 @@ org.sql2o sql2o-parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT diff --git a/extensions/postgres/pom.xml b/extensions/postgres/pom.xml index 22e3e35c..c230dc97 100644 --- a/extensions/postgres/pom.xml +++ b/extensions/postgres/pom.xml @@ -18,7 +18,7 @@ org.sql2o.extensions extensions-parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java b/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java index 6b43500b..83bc526d 100644 --- a/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java +++ b/extensions/postgres/src/test/java/org/sql2o/extensions/postgres/PostgresTest.java @@ -203,23 +203,23 @@ public void testUUID() { } - @Test - public void testArray() { - try (Connection con = sql2o.open()) { - con.createQuery("create table arraytest (id serial primary key, data text[])").executeUpdate(); - - final var data = new String[]{"foo", "bar", "baz"}; - - con.createQuery("insert into arraytest(data) values (:data)") - .addParameter("data", data) - .executeUpdate(); - - var fetchedData = con.createQuery("select data from arraytest") - .executeScalar(String[].class); - - assertThat(fetchedData, is(equalTo(data))); - } - } +// @Test +// public void testArray() { +// try (Connection con = sql2o.open()) { +// con.createQuery("create table arraytest (id serial primary key, data text[])").executeUpdate(); +// +// final var data = new String[]{"foo", "bar", "baz"}; +// +// con.createQuery("insert into arraytest(data) values (:data)") +// .addParameter("data", data) +// .executeUpdate(); +// +// var fetchedData = con.createQuery("select data from arraytest") +// .executeScalar(String[].class); +// +// assertThat(fetchedData, is(equalTo(data))); +// } +// } diff --git a/pom.xml b/pom.xml index 2ab95a4b..5f6a24d1 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.sql2o sql2o-parent Sql2o parent - 1.8.0-SNAPSHOT + 1.9.0-SNAPSHOT Easy database query library From 657d89c8b2c6bed3af59a6eb122d4738b2d1fb40 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Fri, 20 Sep 2024 13:42:00 +0200 Subject: [PATCH 05/10] Removed unused code, and some refactoring in connection with reflection rewrite --- core/src/main/java/org/sql2o/Query.java | 2 +- core/src/main/java/org/sql2o/data/Row.java | 10 +- .../org/sql2o/reflection/FactoryFacade.java | 61 ---- .../org/sql2o/reflection/FieldGetter.java | 32 --- .../sql2o/reflection/FieldGetterFactory.java | 10 - .../org/sql2o/reflection/FieldSetter.java | 34 --- .../sql2o/reflection/FieldSetterFactory.java | 14 - .../java/org/sql2o/reflection/Getter.java | 12 - .../org/sql2o/reflection/MethodGetter.java | 37 --- .../sql2o/reflection/MethodGetterFactory.java | 10 - .../org/sql2o/reflection/MethodSetter.java | 38 --- .../sql2o/reflection/MethodSetterFactory.java | 14 - .../sql2o/reflection/ObjectConstructor.java | 12 - .../reflection/ObjectConstructorFactory.java | 12 - .../main/java/org/sql2o/reflection/Pojo.java | 84 ------ .../org/sql2o/reflection/PojoMetadata.java | 264 ------------------ .../ReflectionFieldGetterFactory.java | 12 - .../ReflectionFieldSetterFactory.java | 16 -- .../ReflectionMethodGetterFactory.java | 12 - .../ReflectionMethodSetterFactory.java | 16 -- .../ReflectionObjectConstructorFactory.java | 30 -- .../java/org/sql2o/reflection/Setter.java | 10 - .../org/sql2o/reflection/package-info.java | 4 - .../PojoIntrospector.java | 2 +- .../AbstractFieldGetterFactoryTest.java | 74 ----- .../AbstractFieldSetterFactoryTest.java | 106 ------- .../AbstractMethodGetterFactoryTest.java | 116 -------- .../AbstractMethodSetterFactoryTest.java | 149 ---------- .../AbstractObjectConstructorFactoryTest.java | 30 -- .../reflect/ReadColumnAnnotationTest.java | 102 ------- .../ReflectionConstructorFactoryTest.java | 14 - .../ReflectionFieldGetterFactoryTest.java | 12 - .../ReflectionFieldSetterFactoryTest.java | 14 - .../ReflectionMethodGetterFactoryTest.java | 12 - .../ReflectionMethodSetterFactoryTest.java | 14 - extensions/oracle-joda-time/pom.xml | 6 - 36 files changed, 6 insertions(+), 1391 deletions(-) delete mode 100644 core/src/main/java/org/sql2o/reflection/FactoryFacade.java delete mode 100644 core/src/main/java/org/sql2o/reflection/FieldGetter.java delete mode 100644 core/src/main/java/org/sql2o/reflection/FieldGetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/FieldSetter.java delete mode 100644 core/src/main/java/org/sql2o/reflection/FieldSetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/Getter.java delete mode 100644 core/src/main/java/org/sql2o/reflection/MethodGetter.java delete mode 100644 core/src/main/java/org/sql2o/reflection/MethodGetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/MethodSetter.java delete mode 100644 core/src/main/java/org/sql2o/reflection/MethodSetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ObjectConstructor.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ObjectConstructorFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/Pojo.java delete mode 100644 core/src/main/java/org/sql2o/reflection/PojoMetadata.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ReflectionFieldGetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ReflectionFieldSetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ReflectionMethodGetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ReflectionMethodSetterFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/ReflectionObjectConstructorFactory.java delete mode 100644 core/src/main/java/org/sql2o/reflection/Setter.java delete mode 100644 core/src/main/java/org/sql2o/reflection/package-info.java rename core/src/main/java/org/sql2o/{reflection => reflection2}/PojoIntrospector.java (99%) delete mode 100644 core/src/test/java/org/sql2o/reflect/AbstractFieldGetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/AbstractFieldSetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/AbstractMethodGetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/AbstractMethodSetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/AbstractObjectConstructorFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/ReflectionConstructorFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/ReflectionFieldGetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/ReflectionFieldSetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/ReflectionMethodGetterFactoryTest.java delete mode 100644 core/src/test/java/org/sql2o/reflect/ReflectionMethodSetterFactoryTest.java diff --git a/core/src/main/java/org/sql2o/Query.java b/core/src/main/java/org/sql2o/Query.java index b1929f01..cb50bbe2 100644 --- a/core/src/main/java/org/sql2o/Query.java +++ b/core/src/main/java/org/sql2o/Query.java @@ -9,7 +9,7 @@ import org.sql2o.logging.LocalLoggerFactory; import org.sql2o.logging.Logger; import org.sql2o.quirks.Quirks; -import org.sql2o.reflection.PojoIntrospector; +import org.sql2o.reflection2.PojoIntrospector; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; diff --git a/core/src/main/java/org/sql2o/data/Row.java b/core/src/main/java/org/sql2o/data/Row.java index 9d469405..8eef7afb 100644 --- a/core/src/main/java/org/sql2o/data/Row.java +++ b/core/src/main/java/org/sql2o/data/Row.java @@ -143,13 +143,11 @@ public String getString(String columnName){ * View row as a simple map. */ public Map asMap() { - Map map = new HashMap(); + final var map = new HashMap(); Set keys = columnNameToIdxMap.keySet(); - Iterator iterator = keys.iterator(); - while (iterator.hasNext()) { - String colum = iterator.next().toString(); - int index = columnNameToIdxMap.get(colum); - map.put(colum, values[index]); + for (String key : keys) { + int index = columnNameToIdxMap.get(key); + map.put(key, values[index]); } return map; } diff --git a/core/src/main/java/org/sql2o/reflection/FactoryFacade.java b/core/src/main/java/org/sql2o/reflection/FactoryFacade.java deleted file mode 100644 index 847be4ef..00000000 --- a/core/src/main/java/org/sql2o/reflection/FactoryFacade.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -@SuppressWarnings("Unsafe") -public class FactoryFacade { - private final static FactoryFacade instance; - - static { - MethodGetterFactory mg = new ReflectionMethodGetterFactory(); - MethodSetterFactory m = new ReflectionMethodSetterFactory(); - ObjectConstructorFactory o = new ReflectionObjectConstructorFactory(); - FieldGetterFactory fg = new ReflectionFieldGetterFactory(); - FieldSetterFactory f = new ReflectionFieldSetterFactory(); - instance = new FactoryFacade(fg, mg, f, m, o); - } - - private final FieldGetterFactory fieldGetterFactory; - private final MethodGetterFactory methodGetterFactory; - private final FieldSetterFactory fieldSetterFactory; - private final MethodSetterFactory methodSetterFactory; - private final ObjectConstructorFactory objectConstructorFactory; - - public FactoryFacade( - FieldGetterFactory fieldGetterFactory, MethodGetterFactory methodGetterFactory, - FieldSetterFactory fieldSetterFactory, MethodSetterFactory methodSetterFactory, - ObjectConstructorFactory objectConstructorFactory) { - - this.fieldGetterFactory = fieldGetterFactory; - this.methodGetterFactory = methodGetterFactory; - this.fieldSetterFactory = fieldSetterFactory; - this.methodSetterFactory = methodSetterFactory; - this.objectConstructorFactory = objectConstructorFactory; - } - - public static FactoryFacade getInstance() { - return instance; - } - - public Getter newGetter(Field field) { - return fieldGetterFactory.newGetter(field); - } - - public Getter newGetter(Method method) { - return methodGetterFactory.newGetter(method); - } - - public Setter newSetter(Field field) { - return fieldSetterFactory.newSetter(field); - } - - public Setter newSetter(Method method) { - return methodSetterFactory.newSetter(method); - } - - public ObjectConstructor newConstructor(Class cls) { - return objectConstructorFactory.newConstructor(cls); - } -} - diff --git a/core/src/main/java/org/sql2o/reflection/FieldGetter.java b/core/src/main/java/org/sql2o/reflection/FieldGetter.java deleted file mode 100644 index 4e0af874..00000000 --- a/core/src/main/java/org/sql2o/reflection/FieldGetter.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.sql2o.reflection; - -import org.sql2o.Sql2oException; - -import java.lang.reflect.Field; - -/** - * used internally to get property values directly from the field. Only used if no getter method is found. - * - * @author mdelapenya - */ -public class FieldGetter implements Getter { - - private Field field; - - public FieldGetter(Field field) { - this.field = field; - this.field.setAccessible(true); - } - - public Object getProperty(Object obj) { - try { - return this.field.get(obj); - } catch (IllegalAccessException e) { - throw new Sql2oException("could not get field " + this.field.getName() + " on class " + obj.getClass().toString(), e); - } - } - - public Class getType() { - return field.getType(); - } -} \ No newline at end of file diff --git a/core/src/main/java/org/sql2o/reflection/FieldGetterFactory.java b/core/src/main/java/org/sql2o/reflection/FieldGetterFactory.java deleted file mode 100644 index 420bd7bb..00000000 --- a/core/src/main/java/org/sql2o/reflection/FieldGetterFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Field; - -/** - * @author mdelapenya - */ -public interface FieldGetterFactory { - Getter newGetter(Field field); -} \ No newline at end of file diff --git a/core/src/main/java/org/sql2o/reflection/FieldSetter.java b/core/src/main/java/org/sql2o/reflection/FieldSetter.java deleted file mode 100644 index 31af9d97..00000000 --- a/core/src/main/java/org/sql2o/reflection/FieldSetter.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.sql2o.reflection; - -import org.sql2o.Sql2oException; - -import java.lang.reflect.Field; - -/** - * used internally to set property values directly into the field. Only used if no setter method is found. - */ -public class FieldSetter implements Setter{ - - private Field field; - - public FieldSetter(Field field) { - this.field = field; - this.field.setAccessible(true); - } - - public void setProperty(Object obj, Object value) { - if (value == null && this.field.getType().isPrimitive()){ - return; // dont try set null to a primitive field - } - - try { - this.field.set(obj, value); - } catch (IllegalAccessException e) { - throw new Sql2oException("could not set field " + this.field.getName() + " on class " + obj.getClass().toString(), e); - } - } - - public Class getType() { - return field.getType(); - } -} diff --git a/core/src/main/java/org/sql2o/reflection/FieldSetterFactory.java b/core/src/main/java/org/sql2o/reflection/FieldSetterFactory.java deleted file mode 100644 index cf00bdbc..00000000 --- a/core/src/main/java/org/sql2o/reflection/FieldSetterFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Field; - -/** - * Created with IntelliJ IDEA. - * User: dimzon - * Date: 4/6/14 - * Time: 12:39 AM - * To change this template use File | Settings | File Templates. - */ -public interface FieldSetterFactory { - Setter newSetter(Field field); -} diff --git a/core/src/main/java/org/sql2o/reflection/Getter.java b/core/src/main/java/org/sql2o/reflection/Getter.java deleted file mode 100644 index 246a11d3..00000000 --- a/core/src/main/java/org/sql2o/reflection/Getter.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflection; - -/** - * The Getter interface is used by sql2o to get property values when doing automatic column to property mapping - * - * @author mdelapenya - */ -public interface Getter { - - Object getProperty(Object obj); - Class getType(); -} diff --git a/core/src/main/java/org/sql2o/reflection/MethodGetter.java b/core/src/main/java/org/sql2o/reflection/MethodGetter.java deleted file mode 100644 index 6de543f4..00000000 --- a/core/src/main/java/org/sql2o/reflection/MethodGetter.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.sql2o.reflection; - -import org.sql2o.Sql2oException; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * used internally to get property values via its getter method. - * - * @author mdelapenya - */ -public class MethodGetter implements Getter { - - private Method method; - private Class type; - - public MethodGetter(Method method) { - this.method = method; - this.method.setAccessible(true); - type = method.getReturnType(); - } - - public Object getProperty(Object obj) { - try { - return this.method.invoke(obj); - } catch (IllegalAccessException e) { - throw new Sql2oException("error while calling getter method with name " + method.getName() + " on class " + obj.getClass().toString(), e); - } catch (InvocationTargetException e) { - throw new Sql2oException("error while calling getter method with name " + method.getName() + " on class " + obj.getClass().toString(), e); - } - } - - public Class getType() { - return type; - } -} diff --git a/core/src/main/java/org/sql2o/reflection/MethodGetterFactory.java b/core/src/main/java/org/sql2o/reflection/MethodGetterFactory.java deleted file mode 100644 index e1950000..00000000 --- a/core/src/main/java/org/sql2o/reflection/MethodGetterFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Method; - -/** - * @author mdelapenya - */ -public interface MethodGetterFactory { - Getter newGetter(Method method); -} \ No newline at end of file diff --git a/core/src/main/java/org/sql2o/reflection/MethodSetter.java b/core/src/main/java/org/sql2o/reflection/MethodSetter.java deleted file mode 100644 index e2704319..00000000 --- a/core/src/main/java/org/sql2o/reflection/MethodSetter.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.sql2o.reflection; - -import org.sql2o.Sql2oException; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * used internally to set property values via its setter method. - */ -public class MethodSetter implements Setter{ - - private Method method; - private Class type; - - public MethodSetter(Method method) { - this.method = method; - this.method.setAccessible(true); - type = method.getParameterTypes()[0]; - } - - public void setProperty(Object obj, Object value) { - if (value == null && type.isPrimitive()){ - return; // dont try to set null to a setter to a primitive type. - } - try { - this.method.invoke(obj, value); - } catch (IllegalAccessException e) { - throw new Sql2oException("error while calling setter method with name " + method.getName() + " on class " + obj.getClass().toString(), e); - } catch (InvocationTargetException e) { - throw new Sql2oException("error while calling setter method with name " + method.getName() + " on class " + obj.getClass().toString(), e); - } - } - - public Class getType() { - return type; - } -} diff --git a/core/src/main/java/org/sql2o/reflection/MethodSetterFactory.java b/core/src/main/java/org/sql2o/reflection/MethodSetterFactory.java deleted file mode 100644 index d1afc653..00000000 --- a/core/src/main/java/org/sql2o/reflection/MethodSetterFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Method; - -/** - * Created with IntelliJ IDEA. - * User: dimzon - * Date: 4/6/14 - * Time: 12:42 AM - * To change this template use File | Settings | File Templates. - */ -public interface MethodSetterFactory { - Setter newSetter(Method method); -} diff --git a/core/src/main/java/org/sql2o/reflection/ObjectConstructor.java b/core/src/main/java/org/sql2o/reflection/ObjectConstructor.java deleted file mode 100644 index 5538a47d..00000000 --- a/core/src/main/java/org/sql2o/reflection/ObjectConstructor.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflection; - -/** - * Created with IntelliJ IDEA. - * User: dimzon - * Date: 4/6/14 - * Time: 1:26 AM - * To change this template use File | Settings | File Templates. - */ -public interface ObjectConstructor { - Object newInstance(); -} diff --git a/core/src/main/java/org/sql2o/reflection/ObjectConstructorFactory.java b/core/src/main/java/org/sql2o/reflection/ObjectConstructorFactory.java deleted file mode 100644 index 82180f4d..00000000 --- a/core/src/main/java/org/sql2o/reflection/ObjectConstructorFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflection; - -/** - * Created with IntelliJ IDEA. - * User: dimzon - * Date: 4/6/14 - * Time: 1:27 AM - * To change this template use File | Settings | File Templates. - */ -public interface ObjectConstructorFactory { - ObjectConstructor newConstructor(Class cls); -} diff --git a/core/src/main/java/org/sql2o/reflection/Pojo.java b/core/src/main/java/org/sql2o/reflection/Pojo.java deleted file mode 100644 index 0aa08393..00000000 --- a/core/src/main/java/org/sql2o/reflection/Pojo.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.sql2o.reflection; - -import static org.sql2o.converters.Convert.throwIfNull; - -import org.sql2o.Sql2oException; -import org.sql2o.converters.Converter; -import org.sql2o.converters.ConverterException; -import org.sql2o.quirks.Quirks; - -/** - * Used internally to represent a plain old java object. - */ -public class Pojo { - - private PojoMetadata metadata; - private boolean caseSensitive; - private Object object; - - public Pojo(PojoMetadata metadata, boolean caseSensitive, Object object){ - this.caseSensitive = caseSensitive; - this.metadata = metadata; - this.object = object; - } - - public Pojo(PojoMetadata metadata, boolean caseSensitive){ - this.caseSensitive = caseSensitive; - this.metadata = metadata; - ObjectConstructor objectConstructor = metadata.getObjectConstructor(); - object = objectConstructor.newInstance(); - } - - @SuppressWarnings("unchecked") - public void setProperty(String propertyPath, Object value, Quirks quirks){ - // String.split uses RegularExpression - // this is overkill for every column for every row - int index = propertyPath.indexOf('.'); - Setter setter; - if (index > 0){ - final String substring = propertyPath.substring(0, index); - setter = metadata.getPropertySetter(substring); - String newPath = propertyPath.substring(index+1); - - Object subValue = this.metadata.getValueOfProperty(substring, this.object); - if (subValue == null){ - try { - subValue = setter.getType().newInstance(); - } catch (InstantiationException e) { - throw new Sql2oException("Could not instantiate a new instance of class "+ setter.getType().toString(), e); - } catch (IllegalAccessException e) { - throw new Sql2oException("Could not instantiate a new instance of class "+ setter.getType().toString(), e); - } - setter.setProperty(this.object, subValue); - } - - PojoMetadata subMetadata = new PojoMetadata(setter.getType(), this.caseSensitive, this.metadata.isAutoDeriveColumnNames(), this.metadata.getColumnMappings()); - Pojo subPojo = new Pojo(subMetadata, this.caseSensitive, subValue); - subPojo.setProperty(newPath, value, quirks); - } - else{ - setter = metadata.getPropertySetter(propertyPath); - Converter converter; - try { - converter = throwIfNull(setter.getType(), quirks.converterOf(setter.getType())); - } catch (ConverterException e) { - throw new Sql2oException("Cannot convert column " + propertyPath + " to type " + setter.getType(), e); - } - - try { - setter.setProperty(this.object, converter.convert( value )); - } catch (ConverterException e) { - throw new Sql2oException("Error trying to convert column " + propertyPath + " to type " + setter.getType(), e); - } - } - - - } - - - - public Object getObject(){ - return this.object; - } - -} diff --git a/core/src/main/java/org/sql2o/reflection/PojoMetadata.java b/core/src/main/java/org/sql2o/reflection/PojoMetadata.java deleted file mode 100644 index bd31fdd6..00000000 --- a/core/src/main/java/org/sql2o/reflection/PojoMetadata.java +++ /dev/null @@ -1,264 +0,0 @@ -package org.sql2o.reflection; - -import org.sql2o.Sql2oException; -import org.sql2o.tools.AbstractCache; -import org.sql2o.tools.SnakeToCamelCase; - -import javax.persistence.Column; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Stores metadata for a POJO. - */ -public class PojoMetadata { - - private static final Cache caseSensitiveFalse = new Cache(); - private static final Cache caseSensitiveTrue = new Cache(); - private final PropertyAndFieldInfo propertyInfo; - private final Map columnMappings; - private final FactoryFacade factoryFacade = FactoryFacade.getInstance(); - - private final boolean caseSensitive; - private final boolean autoDeriveColumnNames; - private final Class clazz; - - public boolean isCaseSensitive() { - return caseSensitive; - } - - public boolean isAutoDeriveColumnNames() { - return autoDeriveColumnNames; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PojoMetadata that = (PojoMetadata) o; - - return autoDeriveColumnNames == that.autoDeriveColumnNames - && caseSensitive == that.caseSensitive - && clazz.equals(that.clazz) - && columnMappings.equals(that.columnMappings) - && propertyInfo.equals(that.propertyInfo); - - } - - @Override - public int hashCode() { - int result = (caseSensitive ? 1 : 0); - result = 31 * result + clazz.hashCode(); - return result; - } - - public PojoMetadata(Class clazz, boolean caseSensitive, boolean autoDeriveColumnNames, Map columnMappings) { - this.caseSensitive = caseSensitive; - this.autoDeriveColumnNames = autoDeriveColumnNames; - this.clazz = clazz; - this.columnMappings = columnMappings == null ? Collections.emptyMap() : columnMappings; - - this.propertyInfo = getPropertyInfoThroughCache(); - } - - public ObjectConstructor getObjectConstructor() { - return propertyInfo.objectConstructor; - } - - private PropertyAndFieldInfo getPropertyInfoThroughCache() { - return (caseSensitive - ? caseSensitiveTrue - : caseSensitiveFalse) - .get(clazz, this); - } - - private PropertyAndFieldInfo initializePropertyInfo() { - - HashMap propertyGetters = new HashMap(); - HashMap propertySetters = new HashMap(); - HashMap fields = new HashMap(); - - boolean isJpaColumnInClasspath = false; - try { - Class.forName("javax.persistence.Column"); - isJpaColumnInClasspath = true; - } catch (ClassNotFoundException e) { - // javax.persistence.Column is not in the classpath - } - - Class theClass = clazz; - ObjectConstructor objectConstructor = factoryFacade.newConstructor(theClass); - do { - for (Field f : theClass.getDeclaredFields()) { - if(Modifier.isStatic(f.getModifiers())) { - continue; - } - String propertyName = readAnnotatedColumnName(f, isJpaColumnInClasspath); - if(propertyName == null) { - propertyName = f.getName(); - } - propertyName = caseSensitive ? propertyName : propertyName.toLowerCase(); - - propertyGetters.put(propertyName, factoryFacade.newGetter(f)); - propertySetters.put(propertyName, factoryFacade.newSetter(f)); - fields.put(propertyName, f); - } - - // prepare methods. Methods will override fields, if both exists. - for (Method m : theClass.getDeclaredMethods()) { - - if (m.getName().startsWith("get")) { - String propertyName = m.getName().substring(3); - if (caseSensitive) { - propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); - } else { - propertyName = propertyName.toLowerCase(); - } - - propertyGetters.put(propertyName, factoryFacade.newGetter(m)); - } - - if (m.getName().startsWith("set") && m.getParameterTypes().length == 1) { - String propertyName = readAnnotatedColumnName(m, isJpaColumnInClasspath); - if(propertyName == null) { - propertyName = m.getName().substring(3); - } - if (caseSensitive) { - propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); - } else { - propertyName = propertyName.toLowerCase(); - } - - propertySetters.put(propertyName, factoryFacade.newSetter(m)); - } - } - theClass = theClass.getSuperclass(); - } while (!theClass.equals(Object.class)); - - return new PropertyAndFieldInfo(propertyGetters, propertySetters, fields, objectConstructor); - - } - - public Map getColumnMappings() { - return columnMappings; - } - - public Getter getPropertyGetter(String propertyName) { - - Getter getter = getPropertyGetterIfExists(propertyName); - - if (getter != null) { - return getter; - } else { - String errorMsg = "Property with name '" + propertyName + "' not found on class " + this.clazz.toString(); - if (this.caseSensitive) { - errorMsg += " (You have turned on case sensitive property search. Is this intentional?)"; - } - throw new Sql2oException(errorMsg); - } - } - - public Getter getPropertyGetterIfExists(String propertyName) { - - String name = this.caseSensitive ? propertyName : propertyName.toLowerCase(); - - if (this.columnMappings.containsKey(name)) { - name = this.columnMappings.get(name); - } - - if (autoDeriveColumnNames) { - name = SnakeToCamelCase.convert(name); - if (!this.caseSensitive) name = name.toLowerCase(); - } - - return propertyInfo.propertyGetters.get(name); - } - - public Setter getPropertySetter(String propertyName) { - - Setter setter = getPropertySetterIfExists(propertyName); - - if (setter != null) { - return setter; - } else { - String errorMsg = "Property with name '" + propertyName + "' not found on class " + this.clazz.toString(); - if (this.caseSensitive) { - errorMsg += " (You have turned on case sensitive property search. Is this intentional?)"; - } - throw new Sql2oException(errorMsg); - } - } - - public Setter getPropertySetterIfExists(String propertyName) { - - String name = this.caseSensitive ? propertyName : propertyName.toLowerCase(); - - if (this.columnMappings.containsKey(name)) { - name = this.columnMappings.get(name); - } - - if (autoDeriveColumnNames) { - name = SnakeToCamelCase.convert(name); - if (!this.caseSensitive) name = name.toLowerCase(); - } - - return propertyInfo.propertySetters.get(name); - } - - public Class getType() { - return this.clazz; - } - - public Object getValueOfProperty(String propertyName, Object object) { - Getter getter = getPropertyGetter(propertyName); - - return getter.getProperty(object); - } - - /** - * Try to read the {@link javax.persistence.Column} annotation and return the name of the column. - * Returns null if no {@link javax.persistence.Column} annotation is present or if the name of the column is empty - */ - private String readAnnotatedColumnName(AnnotatedElement classMember, boolean isJpaColumnInClasspath) { - if(isJpaColumnInClasspath) { - Column columnInformation = classMember.getAnnotation(Column.class); - if(columnInformation != null && columnInformation.name() != null && !columnInformation.name().isEmpty()) { - return columnInformation.name(); - } - } - return null; - } - - private static class Cache extends AbstractCache { - @Override - protected PropertyAndFieldInfo evaluate(Class key, PojoMetadata param) { - return param.initializePropertyInfo(); - } - } - - private static class PropertyAndFieldInfo { - // since this class is private we can just use field access - // to make HotSpot a little less work for inlining - public final Map propertyGetters; - public final Map propertySetters; - public final Map fields; - public final ObjectConstructor objectConstructor; - - private PropertyAndFieldInfo( - Map propertyGetters, Map propertySetters, - Map fields, ObjectConstructor objectConstructor) { - - this.propertyGetters = propertyGetters; - this.propertySetters = propertySetters; - this.fields = fields; - this.objectConstructor = objectConstructor; - } - } -} - diff --git a/core/src/main/java/org/sql2o/reflection/ReflectionFieldGetterFactory.java b/core/src/main/java/org/sql2o/reflection/ReflectionFieldGetterFactory.java deleted file mode 100644 index 0dd507a2..00000000 --- a/core/src/main/java/org/sql2o/reflection/ReflectionFieldGetterFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Field; - -/** - * @author mdelapenya - */ -public class ReflectionFieldGetterFactory implements FieldGetterFactory { - public Getter newGetter(Field field) { - return new FieldGetter(field); - } -} \ No newline at end of file diff --git a/core/src/main/java/org/sql2o/reflection/ReflectionFieldSetterFactory.java b/core/src/main/java/org/sql2o/reflection/ReflectionFieldSetterFactory.java deleted file mode 100644 index 72454433..00000000 --- a/core/src/main/java/org/sql2o/reflection/ReflectionFieldSetterFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Field; - -/** - * Created with IntelliJ IDEA. - * User: dimzon - * Date: 4/6/14 - * Time: 12:40 AM - * To change this template use File | Settings | File Templates. - */ -public class ReflectionFieldSetterFactory implements FieldSetterFactory { - public Setter newSetter(Field field) { - return new FieldSetter(field); - } -} diff --git a/core/src/main/java/org/sql2o/reflection/ReflectionMethodGetterFactory.java b/core/src/main/java/org/sql2o/reflection/ReflectionMethodGetterFactory.java deleted file mode 100644 index c2b6b1b1..00000000 --- a/core/src/main/java/org/sql2o/reflection/ReflectionMethodGetterFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Method; - -/** - * @author mdelapenya - */ -public class ReflectionMethodGetterFactory implements MethodGetterFactory { - public Getter newGetter(Method method) { - return new MethodGetter(method); - } -} \ No newline at end of file diff --git a/core/src/main/java/org/sql2o/reflection/ReflectionMethodSetterFactory.java b/core/src/main/java/org/sql2o/reflection/ReflectionMethodSetterFactory.java deleted file mode 100644 index 3115228f..00000000 --- a/core/src/main/java/org/sql2o/reflection/ReflectionMethodSetterFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sql2o.reflection; - -import java.lang.reflect.Method; - -/** - * Created with IntelliJ IDEA. - * User: dimzon - * Date: 4/6/14 - * Time: 12:43 AM - * To change this template use File | Settings | File Templates. - */ -public class ReflectionMethodSetterFactory implements MethodSetterFactory { - public Setter newSetter(Method method) { - return new MethodSetter(method); - } -} diff --git a/core/src/main/java/org/sql2o/reflection/ReflectionObjectConstructorFactory.java b/core/src/main/java/org/sql2o/reflection/ReflectionObjectConstructorFactory.java deleted file mode 100644 index 589adc6f..00000000 --- a/core/src/main/java/org/sql2o/reflection/ReflectionObjectConstructorFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.sql2o.reflection; - -import org.sql2o.Sql2oException; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -public class ReflectionObjectConstructorFactory implements ObjectConstructorFactory { - public ObjectConstructor newConstructor(final Class clazz) { - try { - final Constructor ctor = clazz.getDeclaredConstructor(); - ctor.setAccessible(true); - return new ObjectConstructor() { - public Object newInstance() { - try { - return ctor.newInstance((Object[])null); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new Sql2oException("Could not create a new instance of class " + clazz, e); - } - } - }; - } catch (Throwable e) { - if (clazz.getEnclosingClass() == null) { - throw new Sql2oException("Could not find parameter-less constructor of class " + clazz, e); - } else { - throw new Sql2oException("Could not find parameter-less constructor of class " + clazz + ", if your pojo is a nested class, you could try to make it static.", e); - } - } - } -} diff --git a/core/src/main/java/org/sql2o/reflection/Setter.java b/core/src/main/java/org/sql2o/reflection/Setter.java deleted file mode 100644 index b7da73fe..00000000 --- a/core/src/main/java/org/sql2o/reflection/Setter.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sql2o.reflection; - -/** - * The Setter interface is used by sql2o to set property values when doing automatic column to property mapping - */ -public interface Setter { - - void setProperty(Object obj, Object value); - Class getType(); -} diff --git a/core/src/main/java/org/sql2o/reflection/package-info.java b/core/src/main/java/org/sql2o/reflection/package-info.java deleted file mode 100644 index b3e497f5..00000000 --- a/core/src/main/java/org/sql2o/reflection/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Provides the internal functionality used by sql2o to accomplish the automatic row to property mapping. - */ -package org.sql2o.reflection; \ No newline at end of file diff --git a/core/src/main/java/org/sql2o/reflection/PojoIntrospector.java b/core/src/main/java/org/sql2o/reflection2/PojoIntrospector.java similarity index 99% rename from core/src/main/java/org/sql2o/reflection/PojoIntrospector.java rename to core/src/main/java/org/sql2o/reflection2/PojoIntrospector.java index bfb1faa2..35d810b0 100644 --- a/core/src/main/java/org/sql2o/reflection/PojoIntrospector.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoIntrospector.java @@ -1,4 +1,4 @@ -package org.sql2o.reflection; +package org.sql2o.reflection2; import org.sql2o.tools.AbstractCache; diff --git a/core/src/test/java/org/sql2o/reflect/AbstractFieldGetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/AbstractFieldGetterFactoryTest.java deleted file mode 100644 index 5db20d76..00000000 --- a/core/src/test/java/org/sql2o/reflect/AbstractFieldGetterFactoryTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.sql2o.reflect; - -import junit.framework.TestCase; -import org.sql2o.reflection.FieldGetterFactory; -import org.sql2o.reflection.Getter; - -import java.lang.reflect.Field; - -/** - * @author mdelapenya - */ -public abstract class AbstractFieldGetterFactoryTest extends TestCase { - public static class POJO1{ - boolean _boolean; - byte _byte; - short _short; - int _int; - long _long; - float _float; - double _double; - char _char; - Object _obj; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - - if (o == null || getClass() != o.getClass()) return false; - - POJO1 pojo1 = (POJO1) o; - - if (_boolean != pojo1._boolean) return false; - if (_byte != pojo1._byte) return false; - if (_char != pojo1._char) return false; - if (Double.compare(pojo1._double, _double) != 0) return false; - if (Float.compare(pojo1._float, _float) != 0) return false; - if (_int != pojo1._int) return false; - if (_long != pojo1._long) return false; - if (_short != pojo1._short) return false; - if (_obj != null ? !_obj.equals(pojo1._obj) : pojo1._obj != null) return false; - - return true; - } - } - - public void testAllTypes() throws IllegalAccessException { - POJO1 pojo = new POJO1(); - pojo._boolean = true; - pojo._byte = 17; - pojo._short=87; - pojo._int= Integer.MIN_VALUE; - pojo._long= 1337; - pojo._char='a'; - pojo._double=Math.PI; - pojo._float= (float) Math.log(93); - pojo._obj = pojo; - - Field[] fields = pojo.getClass().getDeclaredFields(); - - for (Field field : fields) { - Getter getter = fgf.newGetter(field); - assertSame(field.getType(),getter.getType()); - - Object val1 = field.get(pojo); - assertEquals(val1, getter.getProperty(pojo)); - } - } - - public final FieldGetterFactory fgf; - - protected AbstractFieldGetterFactoryTest(FieldGetterFactory fgf) { - this.fgf = fgf; - } -} \ No newline at end of file diff --git a/core/src/test/java/org/sql2o/reflect/AbstractFieldSetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/AbstractFieldSetterFactoryTest.java deleted file mode 100644 index c3b065bf..00000000 --- a/core/src/test/java/org/sql2o/reflect/AbstractFieldSetterFactoryTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.sql2o.reflect; - -import junit.framework.TestCase; -import org.sql2o.reflection.FieldSetterFactory; -import org.sql2o.reflection.Setter; - -import java.lang.reflect.Field; - -/** - * User: dimzon - * Date: 4/9/14 - * Time: 9:46 PM - */ -public abstract class AbstractFieldSetterFactoryTest extends TestCase { - public static class POJO1{ - boolean _boolean; - byte _byte; - short _short; - int _int; - long _long; - float _float; - double _double; - char _char; - Object _obj; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - POJO1 pojo1 = (POJO1) o; - - if (_boolean != pojo1._boolean) return false; - if (_byte != pojo1._byte) return false; - if (_char != pojo1._char) return false; - if (Double.compare(pojo1._double, _double) != 0) return false; - if (Float.compare(pojo1._float, _float) != 0) return false; - if (_int != pojo1._int) return false; - if (_long != pojo1._long) return false; - if (_short != pojo1._short) return false; - if (_obj != null ? !_obj.equals(pojo1._obj) : pojo1._obj != null) return false; - - return true; - } - } - - - public void testAllTypes() throws IllegalAccessException { - POJO1 pojo1 = new POJO1(); - pojo1._boolean = true; - pojo1._byte = 17; - pojo1._short=87; - pojo1._int= Integer.MIN_VALUE; - pojo1._long= 1337; - pojo1._char='a'; - pojo1._double=Math.PI; - pojo1._float= (float) Math.log(93); - pojo1._obj = pojo1; - - POJO1 pojo2 = new POJO1(); - - assertFalse(pojo1.equals(pojo2)); - - Field[] fields = pojo1.getClass().getDeclaredFields(); - for (Field field : fields) { - Setter setter = fsf.newSetter(field); - assertSame(field.getType(),setter.getType()); - Object val1 = field.get(pojo1); - Object val2 = field.get(pojo2); - assertFalse(val1.equals(val2)); - setter.setProperty(pojo2,val1); - Object val3 = field.get(pojo2); - assertEquals(val1,val3); - } - - assertEquals(pojo1,pojo2); - - // let's reset all values to NULL - // primitive fields will not be affected - for (Field field : fields) { - Setter setter = fsf.newSetter(field); - Object val1 = field.get(pojo1); - assertNotNull(val1); - - setter.setProperty(pojo1,null); - - Object val2 = field.get(pojo1); - if(!setter.getType().isPrimitive()){ - assertNull(val2); - continue; - } - assertNotNull(val2); - // not affected - assertEquals(val1,val2); - } - pojo2._obj = null; - assertEquals(pojo2,pojo1); - } - - - public final FieldSetterFactory fsf; - - protected AbstractFieldSetterFactoryTest(FieldSetterFactory fsf) { - this.fsf = fsf; - } -} diff --git a/core/src/test/java/org/sql2o/reflect/AbstractMethodGetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/AbstractMethodGetterFactoryTest.java deleted file mode 100644 index 39e97677..00000000 --- a/core/src/test/java/org/sql2o/reflect/AbstractMethodGetterFactoryTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.sql2o.reflect; - -import junit.framework.TestCase; -import org.sql2o.reflection.Getter; -import org.sql2o.reflection.MethodGetterFactory; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * @author mdelapenya - */ -public abstract class AbstractMethodGetterFactoryTest extends TestCase { - public static class POJO1{ - public boolean get_boolean() { - return this._boolean; - } - - public byte set_byte() { - return this._byte; - } - - public short set_short() { - return this._short; - } - - public int set_int() { - return this._int; - } - - public long set_long() { - return this._long; - } - - public float set_float() { - return this._float; - } - - public double set_double() { - return this._double; - } - - public char set_char() { - return this._char; - } - - public Object set_obj() { - return this._obj; - } - - boolean _boolean; - byte _byte; - short _short; - int _int; - long _long; - float _float; - double _double; - char _char; - Object _obj; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - POJO1 pojo1 = (POJO1) o; - - if (_boolean != pojo1._boolean) return false; - if (_byte != pojo1._byte) return false; - if (_char != pojo1._char) return false; - if (Double.compare(pojo1._double, _double) != 0) return false; - if (Float.compare(pojo1._float, _float) != 0) return false; - if (_int != pojo1._int) return false; - if (_long != pojo1._long) return false; - if (_short != pojo1._short) return false; - if (_obj != null ? !_obj.equals(pojo1._obj) : pojo1._obj != null) return false; - - return true; - } - } - - - public void testAllTypes() throws IllegalAccessException, NoSuchFieldException { - POJO1 pojo = new POJO1(); - pojo._boolean = true; - pojo._byte = 17; - pojo._short=87; - pojo._int= Integer.MIN_VALUE; - pojo._long= 1337; - pojo._char='a'; - pojo._double=Math.PI; - pojo._float= (float) Math.log(93); - pojo._obj = pojo; - - Method[] methods = pojo.getClass().getDeclaredMethods(); - for (Method method : methods) { - if(!method.getName().startsWith("get_")) continue; - - Field field = pojo.getClass() - .getDeclaredField(method.getName().substring(3)); - - Getter getter = mgf.newGetter(method); - assertSame(field.getType(),getter.getType()); - - Object val1 = field.get(pojo); - assertEquals(val1, getter.getProperty(pojo)); - } - } - - - public final MethodGetterFactory mgf; - - public AbstractMethodGetterFactoryTest(MethodGetterFactory mgf) { - this.mgf = mgf; - } -} \ No newline at end of file diff --git a/core/src/test/java/org/sql2o/reflect/AbstractMethodSetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/AbstractMethodSetterFactoryTest.java deleted file mode 100644 index 3838b8a7..00000000 --- a/core/src/test/java/org/sql2o/reflect/AbstractMethodSetterFactoryTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.sql2o.reflect; - -import junit.framework.TestCase; -import org.sql2o.reflection.MethodSetterFactory; -import org.sql2o.reflection.Setter; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * User: dimzon - * Date: 4/9/14 - * Time: 10:18 PM - */ -public abstract class AbstractMethodSetterFactoryTest extends TestCase { - public static class POJO1{ - public void set_boolean(boolean _boolean) { - this._boolean = _boolean; - } - - public void set_byte(byte _byte) { - this._byte = _byte; - } - - public void set_short(short _short) { - this._short = _short; - } - - public void set_int(int _int) { - this._int = _int; - } - - public void set_long(long _long) { - this._long = _long; - } - - public void set_float(float _float) { - this._float = _float; - } - - public void set_double(double _double) { - this._double = _double; - } - - public void set_char(char _char) { - this._char = _char; - } - - public void set_obj(Object _obj) { - this._obj = _obj; - } - - boolean _boolean; - byte _byte; - short _short; - int _int; - long _long; - float _float; - double _double; - char _char; - Object _obj; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - POJO1 pojo1 = (POJO1) o; - - if (_boolean != pojo1._boolean) return false; - if (_byte != pojo1._byte) return false; - if (_char != pojo1._char) return false; - if (Double.compare(pojo1._double, _double) != 0) return false; - if (Float.compare(pojo1._float, _float) != 0) return false; - if (_int != pojo1._int) return false; - if (_long != pojo1._long) return false; - if (_short != pojo1._short) return false; - if (_obj != null ? !_obj.equals(pojo1._obj) : pojo1._obj != null) return false; - - return true; - } - } - - - public void testAllTypes() throws IllegalAccessException, NoSuchFieldException { - POJO1 pojo1 = new POJO1(); - pojo1._boolean = true; - pojo1._byte = 17; - pojo1._short=87; - pojo1._int= Integer.MIN_VALUE; - pojo1._long= 1337; - pojo1._char='a'; - pojo1._double=Math.PI; - pojo1._float= (float) Math.log(93); - pojo1._obj = pojo1; - - POJO1 pojo2 = new POJO1(); - - assertFalse(pojo1.equals(pojo2)); - - Method[] methods = pojo1.getClass().getDeclaredMethods(); - for (Method method : methods) { - if(!method.getName().startsWith("set_")) continue; - Field field = pojo1.getClass() - .getDeclaredField(method.getName().substring(3)); - Setter setter = msf.newSetter(method); - assertSame(field.getType(),setter.getType()); - Object val1 = field.get(pojo1); - Object val2 = field.get(pojo2); - assertFalse(val1.equals(val2)); - setter.setProperty(pojo2,val1); - Object val3 = field.get(pojo2); - assertEquals(val1,val3); - } - - assertEquals(pojo1,pojo2); - - // let's reset all values to NULL - // primitive fields will not be affected - for (Method method : methods) { - if(!method.getName().startsWith("set_")) continue; - Field field = pojo1.getClass() - .getDeclaredField(method.getName().substring(3)); - Setter setter = msf.newSetter(method); - Object val1 = field.get(pojo1); - assertNotNull(val1); - - setter.setProperty(pojo1,null); - - Object val2 = field.get(pojo1); - if(!setter.getType().isPrimitive()){ - assertNull(val2); - continue; - } - assertNotNull(val2); - // not affected - assertEquals(val1,val2); - } - pojo2._obj = null; - assertEquals(pojo2,pojo1); - } - - - public final MethodSetterFactory msf; - - public AbstractMethodSetterFactoryTest(MethodSetterFactory msf) { - this.msf = msf; - } -} diff --git a/core/src/test/java/org/sql2o/reflect/AbstractObjectConstructorFactoryTest.java b/core/src/test/java/org/sql2o/reflect/AbstractObjectConstructorFactoryTest.java deleted file mode 100644 index f16a0ca8..00000000 --- a/core/src/test/java/org/sql2o/reflect/AbstractObjectConstructorFactoryTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.sql2o.reflect; - -import junit.framework.TestCase; -import org.sql2o.reflection.ObjectConstructorFactory; - -/** - * User: dimzon - * Date: 4/9/14 - * Time: 10:09 PM - */ -public abstract class AbstractObjectConstructorFactoryTest extends TestCase { - // just a class - public static class POJO1{ - - } - - public final ObjectConstructorFactory ocf; - - public AbstractObjectConstructorFactoryTest(ObjectConstructorFactory ocf) { - this.ocf = ocf; - } - - public void testCreate(){ - Object o = ocf.newConstructor(POJO1.class).newInstance(); - assertNotNull(o); - assertSame(POJO1.class,o.getClass()); - } - - -} diff --git a/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java b/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java deleted file mode 100644 index 62b95ac3..00000000 --- a/core/src/test/java/org/sql2o/reflect/ReadColumnAnnotationTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.sql2o.reflect; - -import com.google.common.collect.ImmutableMap; -import junit.framework.TestCase; -import org.junit.Test; -import org.sql2o.reflection.PojoMetadata; - -import javax.persistence.Column; - -@SuppressWarnings("unused") -public class ReadColumnAnnotationTest extends TestCase { - - @Test - public void testNoAnnotationPojo() { - PojoMetadata metadata = newPojoMetadata(NoAnnotation.class); - assertNotNull(metadata.getPropertySetterIfExists("field1")); - } - - @Test - public void testNoAnnotationSetterPojo() { - PojoMetadata metadata = newPojoMetadata(NoAnnotationSetter.class); - assertNotNull(metadata.getPropertySetterIfExists("field1")); - } - - @Test - public void testOnlyOneAnnotationFieldPojo() { - PojoMetadata metadata = newPojoMetadata(OnlyOneAnnotationField.class); - assertNotNull(metadata.getPropertySetterIfExists("field_1")); - } - - @Test - public void testOneAnnotationFieldPojo() { - PojoMetadata metadata = newPojoMetadata(OneAnnotationField.class); - assertNotNull(metadata.getPropertySetterIfExists("field_1")); - assertNotNull(metadata.getPropertySetterIfExists("field2")); - } - - @Test - public void testAnnotationFieldAndSetterPojo() { - PojoMetadata metadata = newPojoMetadata(AnnotationFieldAndASetter.class); - assertNotNull(metadata.getPropertySetterIfExists("field_1")); - assertNotNull(metadata.getPropertySetterIfExists("field2")); - assertNotNull(metadata.getPropertySetterIfExists("field_3")); - assertNotNull(metadata.getPropertySetterIfExists("field4")); - } - - @Test - public void testUppercaseAnnotationFieldPojo() { - PojoMetadata metadata = newPojoMetadata(UpperCaseAnnotationField.class); - assertNotNull(metadata.getPropertySetterIfExists("field_1")); - } - - private PojoMetadata newPojoMetadata(Class clazz) { - return new PojoMetadata(clazz, false, false, ImmutableMap. of()); - } - - private static class NoAnnotation { - private String field1; - } - - private static class NoAnnotationSetter { - private String field1; - - void setField1(String field1) { - this.field1 = field1; - } - } - - private static class OnlyOneAnnotationField { - @Column(name = "field_1") - private String field1; - } - - private static class OneAnnotationField { - @Column(name = "field_1") - private String field1; - private String field2; - } - - private static class AnnotationFieldAndASetter { - @Column(name = "field_1") - private String field1; - private String field2; - private String field3; - private String field4; - - @Column(name = "field_3") - void setField3(String field3) { - this.field3 = field3; - } - - void setField4(String field4) { - this.field4 = field4; - } - } - - private static class UpperCaseAnnotationField { - @Column(name = "FIELD_1") - private String field1; - } - -} diff --git a/core/src/test/java/org/sql2o/reflect/ReflectionConstructorFactoryTest.java b/core/src/test/java/org/sql2o/reflect/ReflectionConstructorFactoryTest.java deleted file mode 100644 index 6769ab8e..00000000 --- a/core/src/test/java/org/sql2o/reflect/ReflectionConstructorFactoryTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sql2o.reflect; - -import org.sql2o.reflection.ReflectionObjectConstructorFactory; - -/** - * User: dimzon - * Date: 4/9/14 - * Time: 10:15 PM - */ -public class ReflectionConstructorFactoryTest extends AbstractObjectConstructorFactoryTest { - public ReflectionConstructorFactoryTest() { - super(new ReflectionObjectConstructorFactory()); - } -} diff --git a/core/src/test/java/org/sql2o/reflect/ReflectionFieldGetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/ReflectionFieldGetterFactoryTest.java deleted file mode 100644 index 6ec497a3..00000000 --- a/core/src/test/java/org/sql2o/reflect/ReflectionFieldGetterFactoryTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflect; - -import org.sql2o.reflection.ReflectionFieldGetterFactory; - -/** - * @author mdelapenya - */ -public class ReflectionFieldGetterFactoryTest extends AbstractFieldGetterFactoryTest { - public ReflectionFieldGetterFactoryTest() { - super(new ReflectionFieldGetterFactory()); - } -} \ No newline at end of file diff --git a/core/src/test/java/org/sql2o/reflect/ReflectionFieldSetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/ReflectionFieldSetterFactoryTest.java deleted file mode 100644 index 9d682687..00000000 --- a/core/src/test/java/org/sql2o/reflect/ReflectionFieldSetterFactoryTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sql2o.reflect; - -import org.sql2o.reflection.ReflectionFieldSetterFactory; - -/** - * User: dimzon - * Date: 4/9/14 - * Time: 9:44 PM - */ -public class ReflectionFieldSetterFactoryTest extends AbstractFieldSetterFactoryTest { - public ReflectionFieldSetterFactoryTest() { - super(new ReflectionFieldSetterFactory()); - } -} diff --git a/core/src/test/java/org/sql2o/reflect/ReflectionMethodGetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/ReflectionMethodGetterFactoryTest.java deleted file mode 100644 index a373ed94..00000000 --- a/core/src/test/java/org/sql2o/reflect/ReflectionMethodGetterFactoryTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sql2o.reflect; - -import org.sql2o.reflection.ReflectionMethodGetterFactory; - -/** - * @author mdelapenya - */ -public class ReflectionMethodGetterFactoryTest extends AbstractMethodGetterFactoryTest { - public ReflectionMethodGetterFactoryTest() { - super(new ReflectionMethodGetterFactory()); - } -} \ No newline at end of file diff --git a/core/src/test/java/org/sql2o/reflect/ReflectionMethodSetterFactoryTest.java b/core/src/test/java/org/sql2o/reflect/ReflectionMethodSetterFactoryTest.java deleted file mode 100644 index 2c36aaa0..00000000 --- a/core/src/test/java/org/sql2o/reflect/ReflectionMethodSetterFactoryTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sql2o.reflect; - -import org.sql2o.reflection.ReflectionMethodSetterFactory; - -/** - * User: dimzon - * Date: 4/9/14 - * Time: 10:24 PM - */ -public class ReflectionMethodSetterFactoryTest extends AbstractMethodSetterFactoryTest { - public ReflectionMethodSetterFactoryTest() { - super(new ReflectionMethodSetterFactory()); - } -} diff --git a/extensions/oracle-joda-time/pom.xml b/extensions/oracle-joda-time/pom.xml index 2d73180c..b66fc483 100644 --- a/extensions/oracle-joda-time/pom.xml +++ b/extensions/oracle-joda-time/pom.xml @@ -43,12 +43,6 @@ ${project.parent.version} test - - org.sql2o.extensions - sql2o-oracle - 1.9.0-SNAPSHOT - test - From 1f48a91bdaa5cad0ea8a1e30994bfbc915c9bd03 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Fri, 20 Sep 2024 14:06:59 +0200 Subject: [PATCH 06/10] Updated java source and target version --- core/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 06e87169..8f38fa0f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -94,8 +94,8 @@ org.apache.maven.plugins maven-compiler-plugin - 17 - 17 + 11 + 11 From 512e0c0bcecf295e31ce90f9703345e20cb9d027 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Sat, 2 Nov 2024 20:12:57 +0100 Subject: [PATCH 07/10] Commented out a test that doesn't run on java 11 --- .../java/org/sql2o/records/RecordsTest.java | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/core/src/test/java/org/sql2o/records/RecordsTest.java b/core/src/test/java/org/sql2o/records/RecordsTest.java index 984d8ed7..15bdbe67 100644 --- a/core/src/test/java/org/sql2o/records/RecordsTest.java +++ b/core/src/test/java/org/sql2o/records/RecordsTest.java @@ -11,47 +11,47 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class RecordsTest { - - - @ParameterizedTest(name = "{0}") - @ArgumentsSource(H2ArgumentsSourceProvider.class) - void deserializing_a_record_with_constructor_should_work(String dbName, String url, String user, String pass) { - // Arrange - final var sql2o = new Sql2o(url, user, pass); - createATableWithSomeData(sql2o); - - // Act - List recordResult; - try (final var con = sql2o.open()) { - recordResult = con.createQuery("SELECT id, name, is_ok as \"isOk\" FROM record_entity") - .executeAndFetch(RecordEntity.class); - } - - // Assert - assertEquals(2, recordResult.size()); - final var firstRecord = recordResult.get(0); - assertEquals(1, firstRecord.id()); - assertEquals("name1", firstRecord.name()); - assertTrue(firstRecord.isOk()); - } - - private void createATableWithSomeData(Sql2o sql2o){ - try (var con = sql2o.open()) { - con.createQuery("CREATE TABLE record_entity (id INT PRIMARY KEY, name VARCHAR(255), is_ok BOOLEAN)") - .executeUpdate(); - con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") - .addParameter("id", 1) - .addParameter("name", "name1") - .addParameter("is_ok", true) - .executeUpdate(); - con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") - .addParameter("id", 2) - .addParameter("name", "name2") - .addParameter("is_ok", false) - .executeUpdate(); - } - - } - - public record RecordEntity(int id, String name, boolean isOk) {} + // Commented out, because the test doesn't run on jdk 11. Todo, find a way to run this test only on jdk 17 and above in ci. + +// @ParameterizedTest(name = "{0}") +// @ArgumentsSource(H2ArgumentsSourceProvider.class) +// void deserializing_a_record_with_constructor_should_work(String dbName, String url, String user, String pass) { +// // Arrange +// final var sql2o = new Sql2o(url, user, pass); +// createATableWithSomeData(sql2o); +// +// // Act +// List recordResult; +// try (final var con = sql2o.open()) { +// recordResult = con.createQuery("SELECT id, name, is_ok as \"isOk\" FROM record_entity") +// .executeAndFetch(RecordEntity.class); +// } +// +// // Assert +// assertEquals(2, recordResult.size()); +// final var firstRecord = recordResult.get(0); +// assertEquals(1, firstRecord.id()); +// assertEquals("name1", firstRecord.name()); +// assertTrue(firstRecord.isOk()); +// } +// +// private void createATableWithSomeData(Sql2o sql2o){ +// try (var con = sql2o.open()) { +// con.createQuery("CREATE TABLE record_entity (id INT PRIMARY KEY, name VARCHAR(255), is_ok BOOLEAN)") +// .executeUpdate(); +// con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") +// .addParameter("id", 1) +// .addParameter("name", "name1") +// .addParameter("is_ok", true) +// .executeUpdate(); +// con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") +// .addParameter("id", 2) +// .addParameter("name", "name2") +// .addParameter("is_ok", false) +// .executeUpdate(); +// } +// +// } +// +// public record RecordEntity(int id, String name, boolean isOk) {} } From f7a84b55bafc9cb71ce267038e8a6a5e77db5318 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Sat, 2 Nov 2024 20:28:42 +0100 Subject: [PATCH 08/10] Ok, so I decided not to try to somehow make this version work with java 11. It supports records, which was not a thing before java 14, and will therefore not run on anything lower than that. Updated target version to 17 --- .github/workflows/pipeline.yml | 10 --- core/pom.xml | 4 +- .../java/org/sql2o/records/RecordsTest.java | 85 +++++++++---------- pom.xml | 4 +- 4 files changed, 46 insertions(+), 57 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index fd2bdcee..645984da 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -45,16 +45,6 @@ jobs: run: java -version - name: Test with Maven java 17 run: mvn test - - name: Setup JDK 11 for testing - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: 'temurin' - cache: maven - - name: Print java version - run: java -version - - name: Test with Maven java 11 - run: mvn test # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - name: Update dependency graph diff --git a/core/pom.xml b/core/pom.xml index 8f38fa0f..06e87169 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -94,8 +94,8 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 17 + 17 diff --git a/core/src/test/java/org/sql2o/records/RecordsTest.java b/core/src/test/java/org/sql2o/records/RecordsTest.java index 15bdbe67..ef81d764 100644 --- a/core/src/test/java/org/sql2o/records/RecordsTest.java +++ b/core/src/test/java/org/sql2o/records/RecordsTest.java @@ -11,47 +11,46 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class RecordsTest { - // Commented out, because the test doesn't run on jdk 11. Todo, find a way to run this test only on jdk 17 and above in ci. - -// @ParameterizedTest(name = "{0}") -// @ArgumentsSource(H2ArgumentsSourceProvider.class) -// void deserializing_a_record_with_constructor_should_work(String dbName, String url, String user, String pass) { -// // Arrange -// final var sql2o = new Sql2o(url, user, pass); -// createATableWithSomeData(sql2o); -// -// // Act -// List recordResult; -// try (final var con = sql2o.open()) { -// recordResult = con.createQuery("SELECT id, name, is_ok as \"isOk\" FROM record_entity") -// .executeAndFetch(RecordEntity.class); -// } -// -// // Assert -// assertEquals(2, recordResult.size()); -// final var firstRecord = recordResult.get(0); -// assertEquals(1, firstRecord.id()); -// assertEquals("name1", firstRecord.name()); -// assertTrue(firstRecord.isOk()); -// } -// -// private void createATableWithSomeData(Sql2o sql2o){ -// try (var con = sql2o.open()) { -// con.createQuery("CREATE TABLE record_entity (id INT PRIMARY KEY, name VARCHAR(255), is_ok BOOLEAN)") -// .executeUpdate(); -// con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") -// .addParameter("id", 1) -// .addParameter("name", "name1") -// .addParameter("is_ok", true) -// .executeUpdate(); -// con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") -// .addParameter("id", 2) -// .addParameter("name", "name2") -// .addParameter("is_ok", false) -// .executeUpdate(); -// } -// -// } -// -// public record RecordEntity(int id, String name, boolean isOk) {} + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(H2ArgumentsSourceProvider.class) + void deserializing_a_record_with_constructor_should_work(String dbName, String url, String user, String pass) { + // Arrange + final var sql2o = new Sql2o(url, user, pass); + createATableWithSomeData(sql2o); + + // Act + List recordResult; + try (final var con = sql2o.open()) { + recordResult = con.createQuery("SELECT id, name, is_ok as \"isOk\" FROM record_entity") + .executeAndFetch(RecordEntity.class); + } + + // Assert + assertEquals(2, recordResult.size()); + final var firstRecord = recordResult.get(0); + assertEquals(1, firstRecord.id()); + assertEquals("name1", firstRecord.name()); + assertTrue(firstRecord.isOk()); + } + + private void createATableWithSomeData(Sql2o sql2o){ + try (var con = sql2o.open()) { + con.createQuery("CREATE TABLE record_entity (id INT PRIMARY KEY, name VARCHAR(255), is_ok BOOLEAN)") + .executeUpdate(); + con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") + .addParameter("id", 1) + .addParameter("name", "name1") + .addParameter("is_ok", true) + .executeUpdate(); + con.createQuery("INSERT INTO record_entity (id, name, is_ok) VALUES (:id, :name, :is_ok)") + .addParameter("id", 2) + .addParameter("name", "name2") + .addParameter("is_ok", false) + .executeUpdate(); + } + + } + + public record RecordEntity(int id, String name, boolean isOk) {} } diff --git a/pom.xml b/pom.xml index 5f6a24d1..0ad7a63c 100644 --- a/pom.xml +++ b/pom.xml @@ -142,8 +142,8 @@ maven-compiler-plugin 3.1 - 11 - 11 + 17 + 17 From 434f77ade3ea2042f5adc867f878bd6a4e8fc0bf Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Sat, 2 Nov 2024 21:49:46 +0100 Subject: [PATCH 09/10] Some performance tuning --- .../sql2o/DefaultResultSetHandlerFactory.java | 176 ------------------ .../java/org/sql2o/converters/Convert.java | 11 +- .../sql2o/converters/IntegerConverter.java | 3 + .../org/sql2o/converters/NumberConverter.java | 21 +-- .../org/sql2o/converters/StringConverter.java | 4 + .../org/sql2o/reflection2/PojoProperty.java | 11 +- 6 files changed, 26 insertions(+), 200 deletions(-) diff --git a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java index f891715f..c3a909bd 100644 --- a/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java +++ b/core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java @@ -2,10 +2,8 @@ import org.sql2o.quirks.Quirks; import org.sql2o.reflection2.ObjectBuildableFactoryDelegate; - import java.sql.ResultSetMetaData; - public class DefaultResultSetHandlerFactory implements ResultSetHandlerFactory { private final Quirks quirks; private final ObjectBuildableFactoryDelegate objectBuilderDelegate; @@ -15,114 +13,9 @@ public DefaultResultSetHandlerFactory(ObjectBuildableFactoryDelegate objectBu this.quirks = quirks; } -// @SuppressWarnings("unchecked") -// private static Setter getSetter( -// final Quirks quirks, -// final String propertyPath, -// final PojoMetadata metadata) { -// int index = propertyPath.indexOf('.'); -// if (index <= 0) { -// // Simple path - fast way -// final Setter setter = metadata.getPropertySetterIfExists(propertyPath); -// // behavior change: do not throw if POJO contains less properties -// if (setter == null) return null; -// final Converter converter = quirks.converterOf(setter.getType()); -// // setter without converter -// if (converter == null) return setter; -// return new Setter() { -// public void setProperty(Object obj, Object value) { -// try { -// setter.setProperty(obj, converter.convert(value)); -// } catch (ConverterException e) { -// throw new Sql2oException("Error trying to convert column " + propertyPath + " to type " + setter.getType(), e); -// } -// } -// -// public Class getType() { -// return setter.getType(); -// } -// }; -// } -// // dot path - long way -// // i'm too lazy now to rewrite this case so I just call old unoptimized code... -// // TODO: rewrite, get rid of POJO class -// return new Setter() { -// public void setProperty(Object obj, Object value) { -// Pojo pojo = new Pojo(metadata, metadata.isCaseSensitive(), obj); -// pojo.setProperty(propertyPath, value, quirks); -// } -// -// public Class getType() { -// // doesn't used anyway -// return Object.class; -// } -// }; -// } - -// private static class Key { -// final String stringKey; -// final DefaultResultSetHandlerFactory f; -// -// DefaultResultSetHandlerFactory factory(){ -// return f; -// } -// -// private PojoMetadata getMetadata() { -// return f.metadata; -// } -// -// private Quirks getQuirksMode() { -// return f.quirks; -// } -// -// private Key(String stringKey, DefaultResultSetHandlerFactory f) { -// this.stringKey = stringKey; -// this.f = f; -// } -// -// @Override -// public boolean equals(Object o) { -// if (this == o) return true; -// if (o == null || getClass() != o.getClass()) return false; -// -// Key key = (Key) o; -// -// return f.metadata.equals(key.getMetadata()) -// && f.quirks == key.getQuirksMode() -// && stringKey.equals(key.stringKey); -// -// } -// -// @Override -// public int hashCode() { -// int result = f.metadata.hashCode(); -// result = 31 * result + f.quirks.hashCode(); -// result = 31 * result + stringKey.hashCode(); -// return result; -// } -// } -// -// -// private static final AbstractCache cache = -// new AbstractCache() { -// @Override -// protected ResultSetHandler evaluate(Key key, ResultSetMetaData param) { -// try { -// return key.factory().newResultSetHandler0(param); -// } catch (SQLException e) { -// throw new RuntimeException(e); -// } -// } -// }; @SuppressWarnings("unchecked") public ResultSetHandler newResultSetHandler(final ResultSetMetaData meta) { -// StringBuilder stringBuilder = new StringBuilder(); -// for (int i = 1; i <= meta.getColumnCount(); i++) { -// stringBuilder.append(quirks.getColumnName(meta,i)).append("\n"); -// } -// return cache.get(new Key(stringBuilder.toString(), this),meta); - return resultSet -> { final var objectBuilder = objectBuilderDelegate.newObjectBuilder(); @@ -142,74 +35,5 @@ public ResultSetHandler newResultSetHandler(final ResultSetMetaData meta) { throw new Sql2oException("Error occurred while creating object from ResultSet", e); } }; - - } - - -// @SuppressWarnings("unchecked") -// private ResultSetHandler newResultSetHandler0(final ResultSetMetaData meta) throws SQLException { -// -// return resultSet -> { -// try { -// while (resultSet.next()) { -// for (int i = 1; i <= metadata.getColumnCount(); i++) { -// String colName = quirks.getColumnName(resultSet.getMetaData(), i); -// objectBuilder.withValue(colName, resultSet.getObject(i)); -// } -// } -// return objectBuilder.build(); -// } catch (ReflectiveOperationException e) { -// throw new Sql2oException("Error occurred while creating object from ResultSet", e); -// } -// }; -// } - - -// final Setter[] setters; -// final Converter converter; -// final boolean useExecuteScalar; -// //TODO: it's possible to cache converter/setters/getters -// // cache key is ResultSetMetadata + Bean type -// -// converter = quirks.converterOf(metadata.getType()); -// final int columnCount = meta.getColumnCount(); -// -// setters = new Setter[columnCount + 1]; // setters[0] is always null -// for (int i = 1; i <= columnCount; i++) { -// String colName = quirks.getColumnName(meta, i); -// -// setters[i] = getSetter(quirks, colName, metadata); -// -// // If more than 1 column is fetched (we cannot fall back to executeScalar), -// // and the setter doesn't exist, throw exception. -// if (this.metadata.throwOnMappingFailure && setters[i] == null && columnCount > 1) { -// throw new Sql2oException("Could not map " + colName + " to any property."); -// } -// } -// /** -// * Fallback to executeScalar if converter exists, -// * we're selecting 1 column, and no property setter exists for the column. -// */ -// useExecuteScalar = converter != null && columnCount == 1 && setters[1] == null; -// return resultSet -> { -// if (useExecuteScalar) { -// try { -// return (T) converter.convert(quirks.getRSVal(resultSet, 1)); -// } catch (ConverterException e) { -// throw new Sql2oException("Error occurred while converting value from database to type " + metadata.getType(), e); -// } -// } -// -// // otherwise we want executeAndFetch with object mapping -// Object pojo = metadata.getObjectConstructor().newInstance(); -// for (int colIdx = 1; colIdx <= columnCount; colIdx++) { -// Setter setter = setters[colIdx]; -// if (setter == null) continue; -// setter.setProperty(pojo, quirks.getRSVal(resultSet, colIdx)); -// } -// -// return (T) pojo; -// }; -// } } diff --git a/core/src/main/java/org/sql2o/converters/Convert.java b/core/src/main/java/org/sql2o/converters/Convert.java index b4ac71c7..d8ae098b 100644 --- a/core/src/main/java/org/sql2o/converters/Convert.java +++ b/core/src/main/java/org/sql2o/converters/Convert.java @@ -127,17 +127,12 @@ public static Converter throwIfNull(Class clazz, Converter converte } public static Converter getConverterIfExists(Class clazz) { - Converter c; - rl.lock(); - try { - c = registeredConverters.get(clazz); - } finally { - rl.unlock(); - } + final var c = (Converter)registeredConverters.get(clazz); + if (c != null) return c; if (clazz.isEnum()) { - return registeredEnumConverterFactory.newConverter((Class) clazz); + return registeredEnumConverterFactory.newConverter((Class)clazz); } return null; } diff --git a/core/src/main/java/org/sql2o/converters/IntegerConverter.java b/core/src/main/java/org/sql2o/converters/IntegerConverter.java index 6f24d4c1..09e6f550 100644 --- a/core/src/main/java/org/sql2o/converters/IntegerConverter.java +++ b/core/src/main/java/org/sql2o/converters/IntegerConverter.java @@ -11,6 +11,9 @@ public IntegerConverter(boolean primitive) { @Override protected Integer convertNumberValue(Number val) { + if (val instanceof Integer intVal) { + return intVal; + } return val.intValue(); } diff --git a/core/src/main/java/org/sql2o/converters/NumberConverter.java b/core/src/main/java/org/sql2o/converters/NumberConverter.java index a20f023f..28118625 100644 --- a/core/src/main/java/org/sql2o/converters/NumberConverter.java +++ b/core/src/main/java/org/sql2o/converters/NumberConverter.java @@ -5,7 +5,7 @@ */ public abstract class NumberConverter extends ConverterBase { - private boolean isPrimitive; + private final boolean isPrimitive; public NumberConverter(boolean primitive) { isPrimitive = primitive; @@ -16,23 +16,16 @@ public V convert(Object val) { return isPrimitive ? convertNumberValue(0) : null; } - // val.getClass().isPrimitive() is ALWAYS false - // since boxing (i.e. Object val=(int)1;) - // changes type from Integet.TYPE to Integer.class - // learn 2 java :) - - else if (/*val.getClass().isPrimitive() || */val instanceof Number ) { - return convertNumberValue((Number)val); + else if (val instanceof Number num) { + return convertNumberValue(num); } - else if (val instanceof String){ - String stringVal = ((String)val).trim(); - stringVal = stringVal.isEmpty() ? null : stringVal; + else if (val instanceof String strVal){ + strVal = strVal.trim(); - if (stringVal == null) { + if (strVal.isEmpty()) { return isPrimitive ? convertNumberValue(0) : null; } - - return convertStringValue(stringVal); + return convertStringValue(strVal); } else{ throw new IllegalArgumentException("Cannot convert type " + val.getClass().toString() + " to " + getTypeDescription()); diff --git a/core/src/main/java/org/sql2o/converters/StringConverter.java b/core/src/main/java/org/sql2o/converters/StringConverter.java index 6378f3de..e51f9fea 100644 --- a/core/src/main/java/org/sql2o/converters/StringConverter.java +++ b/core/src/main/java/org/sql2o/converters/StringConverter.java @@ -17,6 +17,10 @@ public String convert(Object val) throws ConverterException { return null; } + if (val instanceof String stringVal){ + return stringVal; + } + if (val instanceof Clob) { Clob clobVal = (Clob)val; try diff --git a/core/src/main/java/org/sql2o/reflection2/PojoProperty.java b/core/src/main/java/org/sql2o/reflection2/PojoProperty.java index 804c69e8..4566c562 100644 --- a/core/src/main/java/org/sql2o/reflection2/PojoProperty.java +++ b/core/src/main/java/org/sql2o/reflection2/PojoProperty.java @@ -1,7 +1,6 @@ package org.sql2o.reflection2; import org.sql2o.Settings; -import org.sql2o.Sql2o; import org.sql2o.Sql2oException; import org.sql2o.converters.ConverterException; @@ -41,7 +40,7 @@ public String getAnnotatedName() { public void SetProperty(Object obj, Object value) throws ReflectiveOperationException { if (setter != null) { try { - final var propertyType = setter.getParameters()[0].getType(); + final var propertyType = getSetterType(); final var convertedValue = settings.getQuirks().converterOf(propertyType).convert(value); if (convertedValue == null && propertyType.isPrimitive()) { return; // don't try to set null to a setter to a primitive type. @@ -109,4 +108,12 @@ public Object initializeWithNewInstance(Object obj) throws ReflectiveOperationEx throw new Sql2oException("Could not initialize property " + getName() + " no setter or field found."); } + + private Class setterType = null; + private Class getSetterType() { + if (setterType == null) { + setterType = setter.getParameters()[0].getType(); + } + return setterType; + } } From 4956fb3880b07f3cfe20aa323275c96e4e9409c7 Mon Sep 17 00:00:00 2001 From: Lars Aaberg Date: Sun, 3 Nov 2024 11:33:25 +0100 Subject: [PATCH 10/10] Better performant cache, by doing an optimistic read without read lock, which is ok because value will never change when it is written. --- .../main/java/org/sql2o/converters/Convert.java | 2 -- core/src/main/java/org/sql2o/tools/Cache.java | 17 +++++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/sql2o/converters/Convert.java b/core/src/main/java/org/sql2o/converters/Convert.java index d8ae098b..ef5d1f2d 100644 --- a/core/src/main/java/org/sql2o/converters/Convert.java +++ b/core/src/main/java/org/sql2o/converters/Convert.java @@ -11,8 +11,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.math.BigDecimal; -import java.time.OffsetDateTime; -import java.time.OffsetTime; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; diff --git a/core/src/main/java/org/sql2o/tools/Cache.java b/core/src/main/java/org/sql2o/tools/Cache.java index 27e6f572..80dd4647 100644 --- a/core/src/main/java/org/sql2o/tools/Cache.java +++ b/core/src/main/java/org/sql2o/tools/Cache.java @@ -8,25 +8,17 @@ public class Cache { private final Map cacheMap; - private final ReadWriteLock lock; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); public Cache() { cacheMap = new HashMap<>(); - lock = new ReentrantReadWriteLock(); } public Value get(Key key, Callable valueDelegate) { - Value value; + Value value = cacheMap.get(key); - // Check readlock first - lock.readLock().lock(); - try { - value = cacheMap.get(key); - if (value != null) { - return value; - } - } finally { - lock.readLock().unlock(); + if (value != null) { + return value; } // If not in cache, create a write lock, create and cache the object, and return it. @@ -47,4 +39,5 @@ public Value get(Key key, Callable valueDelegate) { lock.writeLock().unlock(); } } + }