commit 4999b692140eaffab5a1d397542b54de8ebd550a Author: Olivier Duval Date: Wed Jun 12 22:32:21 2024 +0200 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..62b2ec9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java' + + // [MODULAR] + id 'org.javamodularity.moduleplugin' version '1.8.15' // [Modularity] - https://github.com/java9-modularity/gradle-modules-plugin + + id 'maven-publish' // [Maven] Publish - https://docs.gradle.org/current/userguide/publishing_maven.html +} + +group = 'fr.doap.jdb' +version = '1.0-SNAPSHOT' + +repositories { + maven { url = 'https://maven.doap.fr/repository/maven-all/' } +} + +ext { + junitVersion = '5.10.2' + h2Version = '2.2.224' + slf4jVersion = '2.0.13' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +// Don't cache SNAPSHOT dependencies +configurations.configureEach { + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' +} + +dependencies { + implementation "org.slf4j:slf4j-api:$slf4jVersion" + + testImplementation platform("org.junit:junit-bom:$junitVersion") + testImplementation 'org.junit.jupiter:junit-jupiter' + + testImplementation "com.h2database:h2:$h2Version" + testImplementation "org.slf4j:slf4j-simple:$slf4jVersion" +} + +test { + useJUnitPlatform() +} + +// [Maven Publish] - https://docs.gradle.org/current/userguide/publishing_maven.html +publishing { + publications { + maven(MavenPublication) { + from components.java + } + } + repositories { + maven { + if ((System.getenv('MAVEN_USERNAME') == null) || (System.getenv('MAVEN_PASSWORD') == null)) { + throw new GradleException("You must define the Windows Environment variables MAVEN_USERNAME and MAVEN_PASSWORD to publish to the repository") + } + + def releasesRepoUrl = "https://maven.doap.fr/repository/maven-releases/" + def snapshotsRepoUrl = "https://maven.doap.fr/repository/maven-snapshots/" + url = version.endsWith('-SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + + credentials { + username System.getenv('MAVEN_USERNAME') + password System.getenv('MAVEN_PASSWORD') + } + } + } +} \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4d23add --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'JDB' + diff --git a/src/main/java/fr/doap/jdb/DBWrap.java b/src/main/java/fr/doap/jdb/DBWrap.java new file mode 100644 index 0000000..f68131f --- /dev/null +++ b/src/main/java/fr/doap/jdb/DBWrap.java @@ -0,0 +1,186 @@ +package fr.doap.jdb; + +import fr.doap.slog.*; + +import java.sql.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.stream.*; + +public class DBWrap { + public static String sJID = "_JID_"; + + public DBWrap(String aConn, String aUser, String aPass) { + try { + if (mConnection != null) mConnection.close(); + mConnection = DriverManager.getConnection(aConn+";DB_CLOSE_ON_EXIT=FALSE", aUser, aPass); + mExecutorService = Executors.newSingleThreadExecutor(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + mExecutorService.shutdown(); + L.t("Waiting for queue to finish..."); + try { while (!mExecutorService.awaitTermination(100, TimeUnit.MILLISECONDS)) ; } catch (Exception aE) { L.t(aE); } + L.t("Queue empty... finishing!"); + + try { + if ((mConnection != null)&&(!mConnection.isClosed())) { + mConnection.close(); + L.d("Connection closed"); + } + } catch (Exception aE) { L.w(aE); } + })); + + ResultSet lRST = mConnection.getMetaData().getTables(null, null, "OBJECTS", null); + if (!lRST.next()) exec("CREATE TABLE OBJECTS ("+sJID+" INT PRIMARY KEY)"); + + exec(() -> { + try { + ResultSet lRSPK = mConnection.getMetaData().getColumns(null, null, "OBJECTS", null); + while (lRSPK.next()) mColumns.add(lRSPK.getString(4)); + } catch (Exception aE) { L.w(aE); } + }); +// System.out.println("Registered Primary keys: "+ sPrimaryKeys); + + } catch (Exception aE) { + aE.printStackTrace(); + } + } + + public DBWrap setSQLTypeResolver(Function aFun) { + mColumnSQLTypeResolver = aFun; + return this; + } + + public void exec(String aS) { + mExecutorService.submit(() -> { + try { + mConnection.createStatement().execute(aS); + L.d("SQL: "+aS); + } catch(Exception aE) { L.w(aE); }}); + } + + public void exec(Runnable aR) { + mExecutorService.submit(aR); + } + + public void insert(Map aKV) { + mExecutorService.submit(() -> { + try { + declareNewColumns(aKV); + + String lSQL = "INSERT INTO OBJECTS ("+String.join(", ", aKV.keySet().stream().map(aS -> "\""+aS+"\"").toList())+") VALUES ("+String.join(", ", Collections.nCopies(aKV.size(), "?"))+")"; + + PreparedStatement lPS = mConnection.prepareStatement(lSQL, new String[]{ sJID }); + List lCol = aKV.values().stream().toList(); + for (int i = 0; i < lCol.size(); i++) lPS.setObject(i + 1, lCol.get(i)); + lPS.execute(); + + L.d("SQL: {} with {}", lSQL, lCol); + + ResultSet lRS = lPS.getGeneratedKeys(); + if (lRS.next()) aKV.put(sJID, lRS.getInt(1)); + } catch(Exception aE) { L.w(aE); } + }); + } + + public void update(Map aKV) { + mExecutorService.submit(() -> { + try { + declareNewColumns(aKV); + + int lJID = (int)aKV.remove(sJID); + String lSQL = "UPDATE OBJECTS SET "+String.join(", ", aKV.keySet().stream().map(aS -> "\""+aS+"\"=?").toList())+" WHERE "+sJID+"="+lJID; + + PreparedStatement lPS = mConnection.prepareStatement(lSQL); + List lCol = aKV.values().stream().toList(); + for (int i = 0; i < lCol.size(); i++) lPS.setObject(i + 1, lCol.get(i)); + lPS.execute(); + + L.d("SQL: {} with {}", lSQL, lCol); + + aKV.put(sJID, lJID); + } catch(Exception aE) { L.w(aE); } + }); + } + + public Stream> query(String aS, Object ... aParams) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator>() { + ResultSet mResultSet; + String mCols[]; + + { + try { mResultSet = _query(aS, aParams).get(); } catch (Exception aE) { L.w(aE); } + } + + @Override + public boolean hasNext() { + try { return !mResultSet.isAfterLast() && !mResultSet.isLast(); } catch (Exception aE) {}; + return false; + } + + @Override + public Map next() { + try { + mResultSet.next(); + + if (mCols == null) { + ResultSetMetaData lRSM = mResultSet.getMetaData(); + mCols = new String[lRSM.getColumnCount()]; + for (int i = 0; i < mCols.length; i++) mCols[i] = lRSM.getColumnName(i + 1); + } + + Map lRes = new HashMap<>(); + for (int i = 0; i < mCols.length; i++) lRes.put(mCols[i], mResultSet.getObject(i + 1)); + + return lRes; + } catch (Exception aE) { L.w(aE); } + return null; + } + }, Spliterator.SORTED), false); + } + + protected Future _query(String aS, Object ... aParams) { + return mExecutorService.submit(() -> { + try { + PreparedStatement lPS = mConnection.prepareStatement(aS, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + if (aParams != null) for (int i=0; i {} lines", aS, aParams, lRS.getRow()); + lRS.beforeFirst(); + + return lRS; + +// return lPS.executeQuery(); + } catch (Exception aE) { L.w(aE); } + return null; + }); + } + + protected void declareNewColumns(Map aKV) { + Set lNewCols = new HashSet<>(aKV.keySet()); + lNewCols.removeAll(mColumns); + + if (!lNewCols.isEmpty()) { + try { + String lSQL = "ALTER TABLE OBJECTS ADD COLUMN (" + String.join(", ", lNewCols.stream().map(aCol -> { + String lType = mColumnSQLTypeResolver.apply(aCol); + mColumns.add(aCol); + return "\"" + aCol + "\" " + lType; + }).toList()) + ")"; + + mConnection.createStatement().execute(lSQL); + L.d("SQL: " + lSQL); + } catch (Exception aE) { + L.w(aE); + } + } + } + + protected Connection mConnection; + protected ExecutorService mExecutorService; + protected Set mColumns = new HashSet<>(); + protected Function mColumnSQLTypeResolver; +} \ No newline at end of file diff --git a/src/main/java/fr/doap/jdb/JDB.java b/src/main/java/fr/doap/jdb/JDB.java new file mode 100644 index 0000000..04ec531 --- /dev/null +++ b/src/main/java/fr/doap/jdb/JDB.java @@ -0,0 +1,287 @@ +package fr.doap.jdb; + +import fr.doap.slog.*; + +import javax.sql.rowset.serial.*; +import javax.swing.plaf.*; +import java.lang.reflect.*; +import java.sql.*; +import java.util.*; +import java.util.concurrent.*; + +import static fr.doap.jdb.Utils.*; + +public class JDB { + /* public static */ + static public void init(String aConn, String aUser, String aPass) { + sJDB = new JDB(); + sJDB._init(aConn, aUser, aPass); + } + + static public void save(Object aObject) { + sJDB._save(aObject); + } + + static protected JDB sJDB; + + /* protected member */ + protected void _init(String aConn, String aUser, String aPass) { + mDBWrap = new DBWrap(aConn, aUser, aPass).setSQLTypeResolver(aCol -> mSQLTypes.get(aCol)); + mSQLTypes.put("_CLASS_", "VARCHAR"); + mSQLTypes.put("_SUBS_", "VARBINARY"); + + Optional> lOpt = mDBWrap.query("SELECT MAX("+DBWrap.sJID+")+1 AS NEXTID FROM OBJECTS").findFirst(); + Object lNID = lOpt.get().get("NEXTID"); + if (lNID != null) mNextID = (int)lNID; + else mNextID = 1; + } + + protected void _save(Object aObject) { + new ObjectMapBuilder(aObject); + } + + // aConds = String: le SQL et ensuite les paramètres + protected Iterator _query(Class aClass, Object ... aConds) { + return new Iterator() { + ResultSet mRS; + { + try { + mRS = _buildQuery(Utils.getShortClassname(aClass), aConds).get(); + } + catch (Exception aE) { L.w(aE); } + } + + @Override + public boolean hasNext() { + try { return !(mRS.isLast() || mRS.isAfterLast()); } + catch (Exception aE) { return false; } + } + + @Override + public T next() { + try { + mRS.next(); + int lID = mRS.getInt(mDBWrap.sJID); +L.i("ID: {}", lID); + Object lCached = mWeakObjectCache.getObject(lID); + if (lCached != null) return (T)lCached; +L.i("NOT CACHED ! Loading..."); + + Map> lProps = new HashMap<>(); + Set lCols = new HashSet<>(); + int lColCount = mRS.getMetaData().getColumnCount(); + for (int i=0; i lMap = new HashMap<>(); + lProps.put(lID, lMap); + for (String iCol: lCols) { + Object lP = mRS.getObject(iCol); + if (lP != null) lMap.put(iCol, lP); + } +L.i("Props: {}", lMap); + + List lLoadList = new ArrayList<>(); + byte[] lBA = mRS.getBytes("_SUBS_"); + if (lBA != null) { + int[] lIDs = (int[]) Utils.fromByteArray(lBA); + for (int i=0; i aID.toString()).toList())+")").get(); + while (!lLoadRS.isLast() && !lLoadRS.isAfterLast()) { + lLoadRS.next(); + + lMap = new HashMap<>(); + int lSID = lLoadRS.getInt(mDBWrap.sJID); + lProps.put(lSID, lMap); + for (String iCol: lCols) { + Object lP = lLoadRS.getObject(iCol); + if (lP != null) lMap.put(iCol, lP); + } +L.i(" --> Props: {}", lMap); + + Class lClass = Class.forName(lLoadRS.getString("_CLASS_")); + Object lO = lClass.getDeclaredConstructor().newInstance(); + mWeakObjectCache.put(lSID, lO); + } + lProps.forEach((aID, aProps) -> { + Object lO = mWeakObjectCache.getId(aID); + if ((lO instanceof Collection)&&(lO.getClass().getCanonicalName().startsWith("java."))) { + + } else { + aProps.forEach((aK, aV) -> { + if (!aK.startsWith("_") && aK.contains("#")) { + String[] lKs = aK.split("#"); + try { + Field lF = Class.forName(lKs[0]).getDeclaredField(lKs[1]); + L.i("- key: {} -> {}", aK, lF); + } catch (Exception aE) { L.t(aE); } + } + }); + } + }); + + return (T)mWeakObjectCache.getObject(lID); + } catch (Exception aE) { L.w(aE); } + return null; + } + }; + } + + protected Future _buildQuery(String aClassname, Object ... aConds) { + Object[] lParams = null; + String lSQL = "SELECT * FROM OBJECTS WHERE \""+aClassname+"\"=TRUE"; + if ((aConds != null)&&(aConds.length > 0)) { + lSQL += " AND "+(String)aConds[0]; + if (aConds.length > 1) { + lParams = new Object[aConds.length - 1]; + for (int i = 0; i < lParams.length; i++) lParams[i] = aConds[i + 1]; + } + } + return mDBWrap._query(lSQL, lParams); + } + + static final Map, String> sTypeMap = Utils.HashMapOf( + Boolean.class, "BOOLEAN", + Integer.class, "INTEGER", + Long.class, "BIGINT", + byte[].class, "VARBINARY", + Blob.class, "BLOB", + String.class, "VARCHAR"); + + class ObjectMapBuilder { + ObjectMapBuilder(Object aObject) { + Map> lReach=new HashMap<>(); + Map> lInserts=new HashMap<>(); + Map> lUpdates=new HashMap<>(); + + add(aObject); + + while (mPos < mProcessingList.size()) { + Object lO = mProcessingList.get(mPos); + try { + Constructor lCons = lO.getClass().getDeclaredConstructor(); // ensure at least a default constructor... + } catch (Exception aE) { + throw new RuntimeException("No constructor for class "+lO.getClass().getCanonicalName()); + } + + Set lObjects = new HashSet<>(); + lReach.put(lO, lObjects); + + Map lProps = getProperties(lO); + for (Map.Entry iME: lProps.entrySet()) { + Object lV = iME.getValue(); + if (!sTypeMap.containsKey(lV.getClass())) { + add(lV); + iME.setValue(mWeakObjectCache.getId(lV)); + mSQLTypes.put(iME.getKey(), "INTEGER"); + lObjects.add(lV); + } else mSQLTypes.put(iME.getKey(), sTypeMap.get(lV.getClass())); + } + + lProps.put(DBWrap.sJID, mWeakObjectCache.getId(lO)); + lProps.put("_CLASS_", lO.getClass().getCanonicalName()); + + if (mInserts.contains(lO)) lInserts.put(lO, lProps); + else lUpdates.put(lO, lProps); + + mPos++; + } + + // saturate + Set lReachComplete = new HashSet<>(); + lReachComplete.add(this); + while (!lReachComplete.isEmpty()) { + lReachComplete.clear(); + lReach.values().forEach(aSet -> { + Set lNew = new HashSet<>(aSet); + aSet.forEach(aE -> { + if (lNew.addAll(lReach.get(aE))) lReachComplete.add(aE); + }); + aSet.clear(); + aSet.addAll(lNew); + }); + } + + lReach.forEach((aO, aSet) -> { + if (aSet.size() > 0) { + Object[] lObs = aSet.toArray(); + int lNew[] = new int[lObs.length]; + for (int i = 0; i < lNew.length; i++) lNew[i] = mWeakObjectCache.getId(lObs[i]); +L.i("Reach {} -> {}", aSet, lNew); + if (lInserts.containsKey(aO)) lInserts.get(aO).put("_SUBS_", Utils.toByteArray(lNew)); + else lUpdates.get(aO).put("_SUBS_", Utils.toByteArray(lNew)); + } + }); + + L.i("Reach: {}", lReach); + lInserts.values().forEach(aProps -> mDBWrap.insert(aProps)); + lUpdates.values().forEach(aProps -> mDBWrap.update(aProps)); + } + + private void add(Object aObject) { + if (!mProcessingList.contains(aObject)) { + mProcessingList.add(aObject); + if (mWeakObjectCache.getId(aObject) == -1) { + mWeakObjectCache.put(mNextID, aObject); + mNextID++; + mInserts.add(aObject); + } + } + } + + protected Map getProperties(Object aObject) { + Map lProps = new HashMap<>(); + boolean isCollection = aObject instanceof Collection; + Class lC = aObject.getClass(); + while (lC != Object.class) { + lProps.put(getShortClassname(lC), true); + if (isCollection && lC.getCanonicalName().startsWith("java.")) { + Collection lColl = (Collection) aObject; + Object[] lObjects = new Object[2*lColl.size()]; + Iterator iIt = lColl.iterator(); + for (int i=0; i mProcessingList = new ArrayList<>(); + Set mInserts = new HashSet<>(); + } + + protected DBWrap mDBWrap; + protected WeakObjectCache mWeakObjectCache = new WeakObjectCache(); + protected int mNextID; + protected Map mSQLTypes = new HashMap<>(); +} \ No newline at end of file diff --git a/src/main/java/fr/doap/jdb/TestC.java b/src/main/java/fr/doap/jdb/TestC.java new file mode 100644 index 0000000..50551be --- /dev/null +++ b/src/main/java/fr/doap/jdb/TestC.java @@ -0,0 +1,8 @@ +package fr.doap.jdb; + +public class TestC { + int mInt = 3; + long mLong = 10; + String mStr = "mon Test #" + System.nanoTime(); + TestD mTestD = new TestD(); +} diff --git a/src/main/java/fr/doap/jdb/TestC2.java b/src/main/java/fr/doap/jdb/TestC2.java new file mode 100644 index 0000000..0b84215 --- /dev/null +++ b/src/main/java/fr/doap/jdb/TestC2.java @@ -0,0 +1,8 @@ +package fr.doap.jdb; + +import java.util.*; + +public class TestC2 extends TestC { + String mStr2 = "Test derivé"; + List mList = new ArrayList<>(List.of(1, "List Test", new TestD())); +} diff --git a/src/main/java/fr/doap/jdb/TestD.java b/src/main/java/fr/doap/jdb/TestD.java new file mode 100644 index 0000000..b7b9163 --- /dev/null +++ b/src/main/java/fr/doap/jdb/TestD.java @@ -0,0 +1,5 @@ +package fr.doap.jdb; + +public class TestD { + String mS = "mon test D"; +} diff --git a/src/main/java/fr/doap/jdb/TestSupport.java b/src/main/java/fr/doap/jdb/TestSupport.java new file mode 100644 index 0000000..604e813 --- /dev/null +++ b/src/main/java/fr/doap/jdb/TestSupport.java @@ -0,0 +1,5 @@ +package fr.doap.jdb; + +public class TestSupport { + +} diff --git a/src/main/java/fr/doap/jdb/Utils.java b/src/main/java/fr/doap/jdb/Utils.java new file mode 100644 index 0000000..8675686 --- /dev/null +++ b/src/main/java/fr/doap/jdb/Utils.java @@ -0,0 +1,42 @@ +package fr.doap.jdb; + +import fr.doap.slog.*; + +import java.io.*; +import java.util.*; + +public class Utils { + + static public Map HashMapOf(K aK, V aV, Object ... aObjects) { + Map lMap = new HashMap<>(); + lMap.put(aK, aV); + for (int i=0; i aClass) { + return aClass.getCanonicalName(); + /* + String[] lPackages = aClass.getPackageName().split("[.]"); + return String.join("", Arrays.asList(lPackages).stream().map(aP -> aP.substring(0,1)).toList())+"."+aClass.getSimpleName(); + */ + } + + static byte[] toByteArray(final Object obj) { + ByteArrayOutputStream lBAOS = new ByteArrayOutputStream(); + try (ObjectOutputStream lOOS = new ObjectOutputStream(lBAOS)) { + lOOS.writeObject(obj); + lOOS.flush(); + return lBAOS.toByteArray(); + } catch (Exception ex) { L.w(ex); } + return null; + } + + static Object fromByteArray(byte[] bytes) { + ByteArrayInputStream lBAIS = new ByteArrayInputStream(bytes); + try (ObjectInput lOI = new ObjectInputStream(lBAIS)) { + return lOI.readObject(); + } catch (Exception ex) { L.w(ex); } + return null; + } +} diff --git a/src/main/java/fr/doap/jdb/WeakObjectCache.java b/src/main/java/fr/doap/jdb/WeakObjectCache.java new file mode 100644 index 0000000..21815a8 --- /dev/null +++ b/src/main/java/fr/doap/jdb/WeakObjectCache.java @@ -0,0 +1,34 @@ +package fr.doap.jdb; + +import java.lang.ref.*; +import java.util.*; + +public class WeakObjectCache { + + public Object getObject(int aID) { + WeakReference lWR = mObjectCache.get(aID); + if (lWR != null) return lWR.get(); + return null; + } + + public int getId(Object aObject) { + Integer lID = mIDs.get(aObject); + if (lID != null) return lID; + else return -1; + } + + public Object put(int aID, Object aObject) { + Object lOld = getObject(aID); + mIDs.put(aObject, aID); + mObjectCache.put(aID,new WeakReference<>(aObject)); + return lOld; + } + + public void clear() { + mIDs.clear(); + mObjectCache.clear(); + } + + Map mIDs = new WeakHashMap<>(); + Map> mObjectCache = new TreeMap<>(); +} \ No newline at end of file diff --git a/src/main/java/fr/doap/jdb/impl/ClassAnalyzer.java b/src/main/java/fr/doap/jdb/impl/ClassAnalyzer.java new file mode 100644 index 0000000..38d40a7 --- /dev/null +++ b/src/main/java/fr/doap/jdb/impl/ClassAnalyzer.java @@ -0,0 +1,38 @@ +package fr.doap.jdb.impl; + +import fr.doap.slog.*; + +import java.lang.reflect.*; +import java.util.*; + +abstract +public class ClassAnalyzer { + + abstract public Map analyze(Class aClass); + + public boolean isApplicable(Class aClass) { return true; } + + static public class FieldCA extends ClassAnalyzer { + @Override + public Map analyze(Class aClass) { + Map lMap = new HashMap<>(); + try { + Field[] lFields = aClass.getDeclaredFields(); + for (Field iF: lFields) lMap.put(iF.getName(),new PropGetterSetter.FieldGS(iF)); + } catch (Exception aE) { L.t(aE); } + return lMap; + } + } + + static public class CollectionCA extends ClassAnalyzer { + @Override + public Map analyze(Class aClass) { + return null; + } + + @Override + public boolean isApplicable(Class aClass) { + return Collection.class.isAssignableFrom(aClass); + } + } +} diff --git a/src/main/java/fr/doap/jdb/impl/ClassInfos.java b/src/main/java/fr/doap/jdb/impl/ClassInfos.java new file mode 100644 index 0000000..5bc1a6c --- /dev/null +++ b/src/main/java/fr/doap/jdb/impl/ClassInfos.java @@ -0,0 +1,55 @@ +package fr.doap.jdb.impl; + +import fr.doap.slog.*; + +import java.util.*; + +public class ClassInfos { + + static public Map getProps(Object aObject) { + Map lMap = new HashMap<>(); + ClassInfos lCI = get(aObject.getClass()); + if (lCI != null) { + lCI.mPropsGetterSetter.forEach((aKey, aGS) -> { + lMap.put(aKey, aGS.get(aObject)) + }); + } + return lMap; + } + + static public ClassInfos get(Class aClass) { + if ((aClass != null)&&(aClass != Object.class)) { + ClassInfos lCI = sInfosMap.get(aClass); + if (lCI == null) { + lCI = new ClassInfos(aClass); + sInfosMap.put(aClass, lCI); + } + return lCI; + } + return null; + } + + ClassInfos(Class aClass) { + try { + sInfosMap.put(aClass, this); + Class lSuper = aClass.getSuperclass(); + if (lSuper != Object.class) { + ClassInfos lCI = get(lSuper); + mPropsGetterSetter.putAll(lCI.mPropsGetterSetter); + } + for (ClassAnalyzer iCA: sClassAnalyzers) { + if (iCA.isApplicable(aClass)) { + mPropsGetterSetter.putAll(iCA.analyze(aClass)); + break; + } + } + } catch (Exception aE) { L.t(aE); } + } + + + + private Map mPropsGetterSetter = new HashMap<>(); + + static Map, ClassInfos> sInfosMap = new HashMap<>(); + static List sClassAnalyzers = new ArrayList<>(List.of(new ClassAnalyzer.Collection(), new ClassAnalyzer.Field())); +} diff --git a/src/main/java/fr/doap/jdb/impl/PropGetterSetter.java b/src/main/java/fr/doap/jdb/impl/PropGetterSetter.java new file mode 100644 index 0000000..654a6bf --- /dev/null +++ b/src/main/java/fr/doap/jdb/impl/PropGetterSetter.java @@ -0,0 +1,25 @@ +package fr.doap.jdb.impl; + +import fr.doap.slog.*; + +import java.lang.reflect.*; + +interface PropGetterSetter { + + Class getType(); + + Object get(Object aObject); + void set(Object aObject, Object aValue); + + + static public class FieldGS implements PropGetterSetter { + FieldGS(Field aF) { mField=aF; } + + public Class getType() { return mField.getType(); } + + public Object get(Object aObject) { try { return mField.get(aObject); } catch (Exception aE) { L.t(aE); } return null; } + public void set(Object aObject, Object aValue) { try { mField.set(aObject, aValue); } catch (Exception aE) { L.t(aE); }} + + Field mField; + } +} diff --git a/src/main/java/fr/doap/slog/L.java b/src/main/java/fr/doap/slog/L.java new file mode 100644 index 0000000..0ba6c11 --- /dev/null +++ b/src/main/java/fr/doap/slog/L.java @@ -0,0 +1,106 @@ +package fr.doap.slog; + +import org.slf4j.*; + +public class L { + static public void e(String aS) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isErrorEnabled()) lLogger.error(formatLog(lSTE, aS)); + } + + static public void e(Throwable aThrowable) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isErrorEnabled()) lLogger.error(formatLog(lSTE, aThrowable.getLocalizedMessage()), aThrowable); + } + + static public void e(String aS, Object ... aArgs) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isErrorEnabled()) lLogger.error(formatLog(lSTE, aS), aArgs); + } + + static public void w(String aS) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isWarnEnabled()) lLogger.warn(formatLog(lSTE, aS)); + } + + static public void w(Throwable aThrowable) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isWarnEnabled()) lLogger.warn(formatLog(lSTE, aThrowable.getLocalizedMessage()), aThrowable); + } + + static public void w(String aS, Object ... aArgs) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isWarnEnabled()) lLogger.warn(formatLog(lSTE, aS), aArgs); + } + + static public void i(String aS) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isInfoEnabled()) lLogger.info(formatLog(lSTE, aS)); + } + + static public void i(Throwable aThrowable) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isInfoEnabled()) lLogger.info(formatLog(lSTE, aThrowable.getLocalizedMessage()), aThrowable); + } + + static public void i(String aS, Object ... aArgs) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isInfoEnabled()) lLogger.info(formatLog(lSTE, aS), aArgs); + } + + static public void d(String aS) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isDebugEnabled()) lLogger.debug(formatLog(lSTE, aS)); + } + + static public void d(Throwable aThrowable) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isDebugEnabled()) lLogger.debug(formatLog(lSTE, aThrowable.getLocalizedMessage()), aThrowable); + } + + static public void d(String aS, Object ... aArgs) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isDebugEnabled()) lLogger.debug(formatLog(lSTE, aS), aArgs); + } + + static public void t(String aS) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isTraceEnabled()) lLogger.trace(formatLog(lSTE, aS)); + } + + static public void t(Throwable aThrowable) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isTraceEnabled()) lLogger.trace(formatLog(lSTE, aThrowable.getLocalizedMessage()), aThrowable); + } + + static public void t(String aS, Object ... aArgs) { + StackTraceElement lSTE = getCaller(); + Logger lLogger = LoggerFactory.getLogger(lSTE.getClassName()); + if (lLogger.isTraceEnabled()) lLogger.trace(formatLog(lSTE, aS), aArgs); + } + + static StackTraceElement getCaller() { + StackTraceElement[] lSTEs = Thread.currentThread().getStackTrace(); + int lPos = 1; + while (lSTEs[lPos].getClassName().startsWith("fr.doap.slog.L")) lPos++; + return lSTEs[lPos]; + } + + static String formatLog(StackTraceElement aSTE, String aS) { + return aSTE.getMethodName()+": "+aS+" ("+aSTE.getFileName()+":"+aSTE.getLineNumber()+")"; + } +} diff --git a/src/main/java/fr/doap/trash/DBWrapper.java b/src/main/java/fr/doap/trash/DBWrapper.java new file mode 100644 index 0000000..91d003f --- /dev/null +++ b/src/main/java/fr/doap/trash/DBWrapper.java @@ -0,0 +1,163 @@ +package fr.doap.trash; + +import fr.doap.jdb.*; +import fr.doap.slog.*; + +import java.sql.*; +import java.util.*; +import java.util.function.*; + +public class DBWrapper { + + DBWrapper(String aConn, String aUser, String aPass) { + try { + if (mConnection != null) mConnection.close(); + mConnection = DriverManager.getConnection(aConn+";DB_CLOSE_ON_EXIT=FALSE", aUser, aPass); + + mSQLQueue = new ProcessingQueue<>(aSQL -> aSQL.execute()); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + L.t("Waiting for queue to finish..."); + mSQLQueue.stop().lock(); + L.t("Queue empty... finishing!"); + + try { + if ((mConnection != null)&&(!mConnection.isClosed())) { + mConnection.close(); + L.i("Connection closed"); + } + } catch (Exception aE) { aE.printStackTrace(); } + })); + + ResultSet lRST = mConnection.getMetaData().getTables(null, null, "OBJECTS", null); + if (!lRST.next()) mSQLQueue.add(new SQLCommand("CREATE TABLE OBJECTS ("+sJID+" INT PRIMARY KEY NOT NULL, class VARCHAR)")); + + mSQLQueue.add(new SQLExec(aConnection -> { + try { + ResultSet lRSPK = aConnection.getMetaData().getColumns(null, null, "OBJECTS", null); + L.i("--- Columns:"); + while (lRSPK.next()) { + L.i("Table: {} / Column: {} / Type: {}", lRSPK.getString(3), lRSPK.getString(4), JDBCType.valueOf(lRSPK.getInt(5)).getName()); + } + L.i("--- No more columns"); + } catch (Exception aE) { aE.printStackTrace(); } + })); +// System.out.println("Registered Primary keys: "+ sPrimaryKeys); + + } catch (Exception aE) { + aE.printStackTrace(); + } + } + + protected Connection mConnection; + protected WeakObjectCache mWeakObjectCache = new WeakObjectCache(); + protected ProcessingQueue mSQLQueue; + + interface SQLAbstractCommand { + void execute(); + } + + class SQLExec implements SQLAbstractCommand { + SQLExec(Consumer aCallback) { mCallback=aCallback; } + + @Override + public void execute() { + mCallback.accept(mConnection); + } + + Consumer mCallback; + } + + abstract class SQLSimpleCommand implements SQLAbstractCommand { + abstract protected String getSQL(); + + public void execute() { + try { + String lSQL = getSQL(); + L.t("SQL: {}", lSQL); + + mConnection.createStatement().execute(lSQL); + } catch (Exception aE) { + aE.printStackTrace(); + } + } + } + + class SQLCommand extends SQLSimpleCommand { + SQLCommand(String aS) { mCommand = aS; } + + @Override + protected String getSQL() { return mCommand; } + + String mCommand; + } + + class SQLQuery extends SQLCommand { + SQLQuery(String aQuery, Consumer aRS) { + super(aQuery); + mResultSetConsumer = aRS; + } + + @Override + public void execute() { + try { + String lSQL = getSQL(); + L.t("SQL: {}", lSQL); + + mResultSetConsumer.accept(mConnection.createStatement().executeQuery(lSQL)); + } catch (Exception aE) { + aE.printStackTrace(); + } + } + + Consumer mResultSetConsumer; + } + + abstract class SQLParameterizedAbstractCommand implements SQLAbstractCommand { + abstract protected String getSQL(); + + protected SQLParameterizedAbstractCommand(Map aKV) { + mKeyValues = aKV; + } + + public void execute() { + try { + String lSQL = getSQL(); + L.t("SQL: {}", lSQL); + + PreparedStatement lPS = mConnection.prepareStatement(lSQL, new String[]{ sJID }); + List lCol = mKeyValues.values().stream().toList(); + for (int i = 0; i < lCol.size(); i++) lPS.setObject(i + 1, lCol.get(i)); + lPS.execute(); + ResultSet lRS = lPS.getGeneratedKeys(); + if (lRS.next()) mKeyValues.put(sJID, lRS.getInt(1)); + } catch (Exception aE) { + aE.printStackTrace(); + } + } + + Map mKeyValues = new HashMap<>(); + } + + class SQLInsert extends SQLParameterizedAbstractCommand { + protected SQLInsert(Map aKV) { super(aKV); } + + @Override + protected String getSQL() { + return "INSERT INTO objects ("+String.join(", ", mKeyValues.keySet())+") VALUES ("+String.join(", ", Collections.nCopies(mKeyValues.size(), "?"))+")"; + } + } + + class SQLUpdate extends SQLParameterizedAbstractCommand { + protected SQLUpdate(Map aKV) { super(aKV); } + + @Override + protected String getSQL() { + int lID = (int) mKeyValues.get(sJID); + mKeyValues.remove(sJID); + return "UPDATE objects SET "+String.join(", ", mKeyValues.keySet().stream().map(aK -> aK+"=?").toList())+" WHERE "+sJID+"="+lID; + } + } + + static String sJID = "_JID_"; +} diff --git a/src/main/java/fr/doap/trash/ProcessingQueue.java b/src/main/java/fr/doap/trash/ProcessingQueue.java new file mode 100644 index 0000000..e45d7a4 --- /dev/null +++ b/src/main/java/fr/doap/trash/ProcessingQueue.java @@ -0,0 +1,58 @@ +package fr.doap.trash; + +import fr.doap.slog.*; + +import java.util.concurrent.*; +import java.util.concurrent.locks.*; +import java.util.function.*; + +public class ProcessingQueue { + + public ProcessingQueue(Consumer aProcessor) { + mProcessor = aProcessor; + mThread = new Thread(() -> { + mLock.lock(); + L.t("Starting ProcessingQueue {}", mThread); + try { + while (mStillRunning) { + T lT = mQueue.poll(50, TimeUnit.MILLISECONDS); + if (lT != null) process(lT); + } + } catch (Exception aE) {} + L.t("Flushing ProcessingQueue {}", mThread); + mQueue.forEach(aT -> process(aT)); + L.t("Exiting ProcessingQueue {}", mThread); + mLock.unlock(); + }); + mThread.setDaemon(true); + mThread.start(); + } + + public boolean add(T aT) { + if (mStillRunning) { + L.t("Adding {} to ProcessingQueue {}", aT, mThread); + mQueue.add(aT); + return true; + } + return false; + } + + public void process(T aT) { + L.t("Processing {} with ProcessingQueue {}", aT, mThread); + mProcessor.accept(aT); + } + + public Lock stop() { + if ((mThread != null)&&(mThread.isAlive())) { + L.t("Stopping ProcessingQueue {}", mThread); + mStillRunning = false; + } + return mLock; + } + + Thread mThread; + BlockingQueue mQueue = new LinkedBlockingQueue<>(); + Consumer mProcessor; + boolean mStillRunning = true; + ReentrantLock mLock = new ReentrantLock(); +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..2ed22bc --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,11 @@ +module fr.doap.jdb { + requires java.sql; + requires org.slf4j; + requires java.rmi; + requires java.desktop; + requires java.sql.rowset; + + exports fr.doap.jdb; + exports fr.doap.trash; + exports fr.doap.jdb.impl; +} \ No newline at end of file diff --git a/src/main/java/package-info.java b/src/main/java/package-info.java new file mode 100644 index 0000000..e3c6751 --- /dev/null +++ b/src/main/java/package-info.java @@ -0,0 +1 @@ +package fr.doap.jdb; \ No newline at end of file diff --git a/src/test/java/fr/doap/jdb/DBWrapTest.java b/src/test/java/fr/doap/jdb/DBWrapTest.java new file mode 100644 index 0000000..5f345d7 --- /dev/null +++ b/src/test/java/fr/doap/jdb/DBWrapTest.java @@ -0,0 +1,80 @@ +package fr.doap.jdb; + +import fr.doap.slog.*; +import org.junit.jupiter.api.*; + +import java.sql.*; +import java.util.*; + +@TestMethodOrder(MethodOrderer.MethodName.class) +public class DBWrapTest { + + @BeforeAll + static void setUp() { + if (sDB == null) { + L.i("Creating test db"); + sDB=new DBWrap("jdbc:h2:~/test", "sa", "").setSQLTypeResolver(aS -> "VARCHAR"); + } + } + static DBWrap sDB; + + @Test + public void test1() { + Map lMap = new HashMap<>(); + lMap.put("key1", "val1"); + lMap.put("key1.1", "val1.1"); + lMap.put("key2", "val2"); + lMap.put("_JID_", 1); + sDB.insert(lMap); + } + + @Test + public void test2() { + Map lMap = new HashMap<>(); + lMap.put("key1", "val1bis"); + lMap.put("key3", "val3"); + lMap.put("_JID_", 2); + sDB.insert(lMap); + } + + @Test + public void test3() { + try { + ResultSet lRS = sDB._query("SELECT * FROM OBJECTS").get(); + String lCols[] = null; + while (lRS.next()) { + if (lCols == null) { + lCols = new String[lRS.getMetaData().getColumnCount()]; + for (int i=0; i lRes = new ArrayList<>(); + for (int i=0; i L.i(aKV.toString())); + } + + @Test + public void test5() { + sDB.query("SELECT MAX(_JID_)+1 AS COUNT FROM OBJECTS").forEach(aKV -> L.i(aKV.toString())); + } + + @AfterAll + static void closeUp() { + if (sDB != null) { + L.i("deleting test db: "); + try { + sDB.exec("DROP ALL OBJECTS DELETE FILES"); + L.i("DONE"); + } catch(Exception aE) { + L.i(aE.getLocalizedMessage()); + aE.printStackTrace(); + } + } + } +} diff --git a/src/test/java/fr/doap/jdb/JDBTest.java b/src/test/java/fr/doap/jdb/JDBTest.java new file mode 100644 index 0000000..fd2c89c --- /dev/null +++ b/src/test/java/fr/doap/jdb/JDBTest.java @@ -0,0 +1,39 @@ +package fr.doap.jdb; + +import fr.doap.slog.*; +import org.junit.jupiter.api.*; + +import java.util.*; + +@TestMethodOrder(MethodOrderer.MethodName.class) +public class JDBTest { + + @BeforeAll + static void setUp() { + JDB.init("jdbc:h2:~/test2", "sa", ""); + } + + @Test + public void test1() { + JDB.save(new TestC2()); + JDB.save(new TestC()); + JDB.save(new TestD()); + + JDB.sJDB.mWeakObjectCache.clear(); + + JDB.sJDB.mDBWrap.query("SELECT * FROM OBJECTS").forEach(aMap -> L.i(aMap.toString())); + + L.i("Iterating..."); + Iterator iIT = JDB.sJDB._query(TestC.class); + + while (iIT.hasNext()) { + TestC lTC = iIT.next(); + } + } + + + @AfterAll + static void closeUp() { + JDB.sJDB.mDBWrap.exec("DROP ALL OBJECTS DELETE FILES"); + } +} diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..ac6e90d --- /dev/null +++ b/src/test/resources/simplelogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.defaultLogLevel=trace +org.slf4j.simpleLogger.showShortLogName=true \ No newline at end of file