Merge pull request #2 from kneitzel/feature/componentsAndMVVM

Refactoring and added sql Helper
This commit is contained in:
Konrad Neitzel 2025-04-02 18:38:57 +02:00 committed by GitHub
commit bc44eb31cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 2664 additions and 69 deletions

View File

@ -10,7 +10,7 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>inject</artifactId> <artifactId>core</artifactId>
<properties> <properties>
<!-- Application Properties --> <!-- Application Properties -->

View File

@ -1,6 +1,6 @@
package de.neitzel.inject; package de.neitzel.core.inject;
import de.neitzel.inject.annotation.Component; import de.neitzel.core.inject.annotation.Component;
import org.reflections.Reflections; import org.reflections.Reflections;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;

View File

@ -1,4 +1,4 @@
package de.neitzel.inject.annotation; package de.neitzel.core.inject.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@ -1,4 +1,4 @@
package de.neitzel.inject.annotation; package de.neitzel.core.inject.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@ -0,0 +1,231 @@
package de.neitzel.core.sql;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Helper class to build and execute parameterized SQL queries with factory-based result mapping.
*/
@Slf4j
@RequiredArgsConstructor
public class Query<T> {
/**
* An Integer Factory that can be used to read Integer Values of a ResultSet that only has Integers (e.g. a min oder max query)
*/
public static final Function<ResultSet, Integer> INTEGER_FACTORY = rs -> {
try {
return rs.getInt(1);
} catch (SQLException e) {
throw new RuntimeException(e);
}
};
private final Connection connection;
private final List<ParameterSetter> parameterSetters = new ArrayList<>();
private Function<ResultSet, T> factory;
private String queryText;
public static void execute(final Connection connection, final String query) throws SQLException {
new Query<Object>(connection)
.setQueryText(query)
.execute();
}
/**
* sets the query.
*/
public Query<T> setQueryText(final String queryText) {
this.queryText = queryText;
return this;
}
/**
* Loads the query content from a resource URL.
*/
public Query<T> setQueryResource(final String resourcePath) {
log.info("loading resource: {}", resourcePath);
try (Scanner scanner = new Scanner(Objects.requireNonNull(getClass().getResourceAsStream(resourcePath)),
StandardCharsets.UTF_8)) {
this.queryText = scanner.useDelimiter("\\A").next();
}
log.info("query text: {}", this.queryText);
return this;
}
/**
* Replaces placeholders in the query.
*/
public Query<T> replace(final String placeholder, final String value) {
this.queryText = this.queryText.replace(placeholder, value);
log.info("query text: {}", this.queryText);
return this;
}
/**
* Adds another parameter setter
*
* @param index Index of parameter in prepared statement
* @param value Value to set.
* @param setter Setter to use on prepared statement.
* @param <X> Type of the Parameter.
* @return The Query.
*/
public <X> Query<T> addParameter(final int index, final X value, final TriSqlFunction<PreparedStatement, Integer, X> setter) {
parameterSetters.add(ps -> setter.apply(ps, index, value));
return this;
}
/**
* Adds a ParameterSetter/
*
* @param setter Setter for a parameter.
* @return the Qyery.
*/
public Query<T> addParameter(ParameterSetter setter) {
parameterSetters.add(setter);
return this;
}
/**
* Adds a result factory by name.
*/
public Query<T> factory(final Function<ResultSet, T> factory) {
this.factory = factory;
return this;
}
/**
* Executes the query and returns a stream of results. This Stream must be closed so that the underlying ResultSet is closed.
*/
private Stream<T> stream() throws SQLException {
ResultSet rs = createPreparedStatement().executeQuery();
TrimmingResultSet trimmingResultSet = new TrimmingResultSet(rs);
return streamFromResultSet(trimmingResultSet, factory);
}
/**
* Creates a PreparedStatement and applies all Parameter.
*
* @return the created PreparedStatement
* @throws SQLException thrown if the PreparedStatement could not be created.
*/
private PreparedStatement createPreparedStatement() throws SQLException {
PreparedStatement ps = connection.prepareStatement(queryText);
for (ParameterSetter setter : parameterSetters) {
setter.apply(ps);
}
return ps;
}
/**
* Executes a query wich has no result.
*
* @throws SQLException Thrown when query cannot be executed.
*/
public void execute() throws SQLException {
try (PreparedStatement ps = createPreparedStatement()) {
ps.execute();
}
}
/**
* Streams the results of a ResultSet using a factory that creates instances for each row of the ResultSet
*
* @param rs ResultSet to stream from
* @param factory Factory to create instances of T.
* @return Stream of T instances.
*/
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
Iterator<T> iterator = new Iterator<>() {
private boolean hasNextChecked = false;
private boolean hasNext;
@Override
@SneakyThrows
public boolean hasNext() {
if (!hasNextChecked) {
hasNext = rs.next();
hasNextChecked = true;
}
return hasNext;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
hasNextChecked = false;
return factory.apply(rs);
}
};
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
return StreamSupport.stream(spliterator, false)
.onClose(() -> {
try {
rs.getStatement().close();
} catch (SQLException ignored) {
// Exception is ignored.
}
try {
rs.close();
} catch (SQLException ignored) {
// Exception is ignored.
}
});
}
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
try (Stream<T> stream = stream()) {
return handler.apply(stream);
}
}
/**
* Function with three parameters and no result which can throw an SQLException
*
* @param <T> type of first parameter
* @param <U> type of second parameter
* @param <V> type of third parameter
*/
@FunctionalInterface
public interface TriSqlFunction<T, U, V> {
/**
* Calls the thre parameter function.
*
* @param t first parameter.
* @param u second parameter.
* @param v third parameter.
* @throws SQLException Function could throw an SQLException.
*/
void apply(T t, U u, V v) throws SQLException;
}
/**
* ParameterSetter is a function Interface that could be used to alter a PreparedStatement.
*/
@FunctionalInterface
public interface ParameterSetter {
/**
* Does the required work on PreparedStatement.
*
* @param ps PreparedStatement to work on.
* @throws SQLException Could be thrown when working on PreparedStatement.
*/
void apply(PreparedStatement ps) throws SQLException;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
package de.neitzel.fx.injectfx; package de.neitzel.core.inject;
import de.neitzel.fx.injectfx.testcomponents.test1ok.SuperClass; import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
import de.neitzel.fx.injectfx.testcomponents.test1ok.TestComponent1_1; import de.neitzel.core.inject.testcomponents.test1ok.TestComponent1_1;
import de.neitzel.fx.injectfx.testcomponents.test1ok.TestInterface1_1; import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
import de.neitzel.fx.injectfx.testcomponents.test1ok.TestInterface1_2; import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
import de.neitzel.fx.injectfx.testcomponents.test1ok.sub.TestComponent1_2; import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
import de.neitzel.inject.InjectableComponentScanner;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -17,7 +16,7 @@ class InjectableComponentScannerTest {
*/ */
@Test @Test
void testLoadComponents() { void testLoadComponents() {
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.injectfx.testcomponents.test1ok"); InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.core.inject.testcomponents.test1ok");
var instantiableComponents = scanner.getInstantiableComponents(); var instantiableComponents = scanner.getInstantiableComponents();
var nonUniqueTypes = scanner.getNotUniqueTypes(); var nonUniqueTypes = scanner.getNotUniqueTypes();
@ -38,7 +37,7 @@ class InjectableComponentScannerTest {
*/ */
@Test @Test
void testComponentsFailWithUnknownParameters() { void testComponentsFailWithUnknownParameters() {
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.injectfx.testcomponents.test2fail"); InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.core.inject.testcomponents.test2fail");
var instantiableComponents = scanner.getInstantiableComponents(); var instantiableComponents = scanner.getInstantiableComponents();
var nonUniqueTypes = scanner.getNotUniqueTypes(); var nonUniqueTypes = scanner.getNotUniqueTypes();

View File

@ -0,0 +1,4 @@
package de.neitzel.core.inject.testcomponents.test1ok;
public class SuperClass {
}

View File

@ -1,6 +1,6 @@
package de.neitzel.fx.injectfx.testcomponents.test1ok; package de.neitzel.core.inject.testcomponents.test1ok;
import de.neitzel.inject.annotation.Component; import de.neitzel.core.inject.annotation.Component;
@Component @Component
public class TestComponent1_1 extends SuperClass implements TestInterface1_2 { public class TestComponent1_1 extends SuperClass implements TestInterface1_2 {

View File

@ -0,0 +1,4 @@
package de.neitzel.core.inject.testcomponents.test1ok;
public interface TestInterface1_1 {
}

View File

@ -0,0 +1,4 @@
package de.neitzel.core.inject.testcomponents.test1ok;
public interface TestInterface1_2 {
}

View File

@ -0,0 +1,12 @@
package de.neitzel.core.inject.testcomponents.test1ok.sub;
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
import de.neitzel.core.inject.annotation.Component;
@Component
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
public TestComponent1_2() {
}
}

View File

@ -1,6 +1,6 @@
package de.neitzel.fx.injectfx.testcomponents.test2fail; package de.neitzel.core.inject.testcomponents.test2fail;
import de.neitzel.inject.annotation.Component; import de.neitzel.core.inject.annotation.Component;
/** /**
* TestComponent1 that should fail. * TestComponent1 that should fail.

View File

@ -16,7 +16,7 @@
<link.name>${project.artifactId}</link.name> <link.name>${project.artifactId}</link.name>
<launcher>${project.artifactId}</launcher> <launcher>${project.artifactId}</launcher>
<appName>${project.artifactId}</appName> <appName>${project.artifactId}</appName>
<main.class>de.neitzel.fx.injectfx.example.Main</main.class> <main.class>de.neitzel.core.fx.injectfx.example.Main</main.class>
<jar.filename>${project.artifactId}-${project.version}</jar.filename> <jar.filename>${project.artifactId}-${project.version}</jar.filename>
</properties> </properties>

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.component.example; package de.neitzel.core.fx.component.example;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@ -1,6 +1,6 @@
package de.neitzel.fx.component.example; package de.neitzel.core.fx.component.example;
import de.neitzel.fx.component.ComponentLoader; import de.neitzel.core.fx.component.ComponentLoader;
import javafx.application.Application; import javafx.application.Application;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.component.example; package de.neitzel.core.fx.component.example;
/** /**
* Another Main class as workaround when the JavaFX Application ist started without * Another Main class as workaround when the JavaFX Application ist started without

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.component.example; package de.neitzel.core.fx.component.example;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.injectfx.example; package de.neitzel.core.fx.injectfx.example;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.injectfx.example; package de.neitzel.core.fx.injectfx.example;
/** /**
* Another Main class as workaround when the JavaFX Application ist started without * Another Main class as workaround when the JavaFX Application ist started without

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.injectfx.example; package de.neitzel.core.fx.injectfx.example;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;

View File

@ -6,7 +6,7 @@
<AnchorPane xmlns="http://javafx.com/javafx" <AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
xmlns:nfx="http://example.com/nfx" xmlns:nfx="http://example.com/nfx"
fx:controller="de.neitzel.fx.component.ComponentController" fx:controller="de.neitzel.core.fx.component.ComponentController"
prefWidth="300" prefHeight="100"> prefWidth="300" prefHeight="100">
<children> <children>
<TextField layoutX="10" layoutY="10" prefWidth="280" <TextField layoutX="10" layoutY="10" prefWidth="280"

View File

@ -3,7 +3,7 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<AnchorPane prefHeight="127.0" prefWidth="209.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.neitzel.fx.injectfx.example.MainWindow"> <AnchorPane prefHeight="127.0" prefWidth="209.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.neitzel.core.fx.injectfx.example.MainWindow">
<children> <children>
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" /> <Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" />
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0" /> <TextField fx:id="textField" layoutX="14.0" layoutY="24.0" />

View File

@ -6,7 +6,7 @@
<AnchorPane xmlns="http://javafx.com/javafx" <AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
xmlns:nfx="http://example.com/nfx" xmlns:nfx="http://example.com/nfx"
fx:controller="de.neitzel.fx.component.ComponentController" fx:controller="de.neitzel.core.fx.component.ComponentController"
prefWidth="300" prefHeight="180"> prefWidth="300" prefHeight="180">
<children> <children>
<TextField layoutX="10" layoutY="10" prefWidth="280" <TextField layoutX="10" layoutY="10" prefWidth="280"

View File

@ -17,14 +17,13 @@
<link.name>${project.artifactId}</link.name> <link.name>${project.artifactId}</link.name>
<launcher>${project.artifactId}</launcher> <launcher>${project.artifactId}</launcher>
<appName>${project.artifactId}</appName> <appName>${project.artifactId}</appName>
<main.class>example.de.neitzel.fx.injectfx.Main</main.class>
<jar.filename>${project.artifactId}-${project.version}</jar.filename> <jar.filename>${project.artifactId}-${project.version}</jar.filename>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>de.neitzel.lib</groupId> <groupId>de.neitzel.lib</groupId>
<artifactId>inject</artifactId> <artifactId>core</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.component; package de.neitzel.core.fx.component;
import javafx.beans.property.*; import javafx.beans.property.*;
import java.lang.reflect.*; import java.lang.reflect.*;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.component; package de.neitzel.core.fx.component;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.component; package de.neitzel.core.fx.component;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;

View File

@ -1,6 +1,6 @@
package de.neitzel.fx.injectfx; package de.neitzel.core.fx.injectfx;
import de.neitzel.inject.InjectableComponentScanner; import de.neitzel.core.inject.InjectableComponentScanner;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.*; import java.util.*;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.injectfx; package de.neitzel.core.fx.injectfx;
import javafx.util.Callback; import javafx.util.Callback;

View File

@ -1,6 +1,6 @@
package de.neitzel.fx.injectfx; package de.neitzel.core.fx.injectfx;
import de.neitzel.inject.InjectableComponentScanner; import de.neitzel.core.inject.InjectableComponentScanner;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.mvvm; package de.neitzel.core.fx.mvvm;
/** /**
* Enum representing the direction of data binding between a JavaFX control and a ViewModel property. * Enum representing the direction of data binding between a JavaFX control and a ViewModel property.

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.mvvm; package de.neitzel.core.fx.mvvm;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.mvvm; package de.neitzel.core.fx.mvvm;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import java.net.URL; import java.net.URL;

View File

@ -1,4 +1,4 @@
package de.neitzel.fx.mvvm; package de.neitzel.core.fx.mvvm;
import javafx.beans.property.*; import javafx.beans.property.*;

View File

@ -1,4 +0,0 @@
package de.neitzel.fx.injectfx.testcomponents.test1ok;
public class SuperClass {
}

View File

@ -1,4 +0,0 @@
package de.neitzel.fx.injectfx.testcomponents.test1ok;
public interface TestInterface1_1 {
}

View File

@ -1,4 +0,0 @@
package de.neitzel.fx.injectfx.testcomponents.test1ok;
public interface TestInterface1_2 {
}

View File

@ -1,12 +0,0 @@
package de.neitzel.fx.injectfx.testcomponents.test1ok.sub;
import de.neitzel.fx.injectfx.testcomponents.test1ok.SuperClass;
import de.neitzel.fx.injectfx.testcomponents.test1ok.TestInterface1_1;
import de.neitzel.fx.injectfx.testcomponents.test1ok.TestInterface1_2;
import de.neitzel.inject.annotation.Component;
@Component
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
public TestComponent1_2() {
}
}

View File

@ -11,7 +11,7 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>inject</module> <module>core</module>
<module>fx</module> <module>fx</module>
<module>fx-example</module> <module>fx-example</module>
</modules> </modules>