diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index d2e7774..a9a21a7 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -28,4 +28,4 @@ jobs: distribution: 'temurin' cache: maven - name: Build with Maven - run: mvn -B package --file pom.xml + run: mvn -B verify --file pom.xml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 371d181..a145bfc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,59 +1,56 @@ -name: release +name: Release on: workflow_dispatch: + inputs: + tag: + type: string + description: tag + +env: + INPUT_VERSION: '0.2.0' jobs: - release: - name: Release + bump_version: + name: "Bump Version" runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + - name: Set up Java + uses: actions/setup-java@v3 with: - fetch-depth: 0 - - # Configure build steps as you'd normally do - - - name: Setup Java + java-version: '17' + distribution: 'adopt' + - name: Bump version + id: bump + run: "./mvnw versions:set -DnewVersion=${{ github.event.inputs.tag }} -DprocessAllModules && ./mvnw versions:commit -DprocessAllModules" + - name: Commit bumped version + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Bump version to ${{ github.event.inputs.tag }}" + publish: + needs: [bump_version] + runs-on: ubuntu-latest + steps: + - name: wait for version bump + run: sleep 5 + - uses: actions/checkout@v3 + - name: Set up Java uses: actions/setup-java@v3 with: - java-version: 21 - distribution: 'zulu' - server-id: central - server-username: MAVEN_USERNAME - server-password: MAVEN_CENTRAL_TOKEN - gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} - gpg-passphrase: MAVEN_GPG_PASSPHRASE - - # Post JARs to Maven Central - - - name: Release to Maven Central - env: - MAVEN_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.SONATYPE_PASSWORD }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - export GPG_TTY=$(tty) - git config user.name "JonasG" - git config user.email "jonas.grgt@gmail.com" - mvn -B --file pom.xml release:prepare release:perform - - # Create a release - - - name: Run JReleaser - uses: jreleaser/release-action@v2 + java-version: '17' + distribution: 'adopt' + - name: Tag Release + uses: tvdias/github-tagger@v0.0.1 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + tag: "v${{ github.event.inputs.tag }}" + - name: Release env: + JRELEASER_NEXUS2_USERNAME: ${{ secrets.JRELEASER_NEXUS2_USERNAME }} + JRELEASER_NEXUS2_PASSWORD: ${{ secrets.JRELEASER_NEXUS2_PASSWORD }} + JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }} JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Persist logs - - - name: JReleaser release output - if: always() - uses: actions/upload-artifact@v3 - with: - name: jreleaser-release - path: | - out/jreleaser/trace.log - out/jreleaser/output.properties + run: ./mvnw -Prelease deploy jreleaser:full-release -DaltDeploymentRepository=local::file:./target/staging-deploy --non-recursive diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..bf82ff0 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..9527f33 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/README.md b/README.md index 887dc5e..8fac05e 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,10 @@ public class Location { private Location() { } - @Tag(path = "/WeatherData/Location/City") + @Tag(path = "City") private String City; - @Tag(path = "/WeatherData/Location/Country") + @Tag(path = "Country") private String Country; } ``` @@ -68,7 +68,7 @@ String document = """ 75 - °F + 60 @@ -134,71 +134,56 @@ class Temperature { ### Collection types -When deserializing XML data containing a collection type, the following conventions apply: +When deserializing an XML document containing repeated elements, it can be mapped onto one of the collection types `List` or `Set`. + +The following conventions should be followed: -- Only `List` and `Set` types are supported -- The List or Set field should be annotated with `@Tag` having a `path` pointing to the containing tag that holds the repeated tags. -- The nested complex type should be annotated top-level with `@Tag` having a `path` pointing to a single element that is repeated -- Fields within the nested complex type can be annotated as usual. +- Only `List` and `Set` types are supported for mapping repeated elements. +- The `@Tag` annotation should be used on a `List` or `Set` field. + - Include a `path` attribute pointing to the containing tag that holds the repeated tags. + - Include an `items` attribute pointing to the repeated tag, relatively. + - The `path` attribute supports both relative and absolute paths. +- The generic argument can be any standard simple type (e.g., `String`, `Boolean`, `Double`, `Long`, etc.) or a custom complex type. +- Fields within the nested complex type can be annotated as usual, using relative or absolute paths. + +Example XML document: ```xml - - - - 71 - - - 62 - - + + + + 71 + + + 62 + + - - 78 - - - 71 - + + 78 + + + 71 + ``` -```java -public class WeatherData { - // When mapping List or Set the type needs to point to the - // tag containing the repeated elements - @Tag(path = "/WeatherData/Forecasts") - List forecasts; -} - -// Top level annoation is required and -// needs to point to an indiviual element that is repeated -@Tag(path = "/WeatherData/Forecasts/Day") -public class Forecast { - // field can be both absolutely as relatively mapped - @Tag(path = "High/Value") - String maxTemperature; - - // field can be both absolutely as relatively mapped - @Tag(path = "/WeatherData/Forecasts/Day/Low/Value") - String minTemperature; -} -``` - ### Map types Maps can be deserialized either as a field or a top-level type. Consider the following XML document: ```xml - - - 75 - °F - - + + + 75 + °F + + ``` diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..b7f0646 --- /dev/null +++ b/mvnw @@ -0,0 +1,287 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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 + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..474c9d6 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,187 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 5f19086..b82de43 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.jonasg xjx - 0.2.0 + 0.1.0 pom xjx-sax @@ -194,18 +194,6 @@ - - - - - - publication - - local::file:./target/staging-deploy - - - deploy - org.apache.maven.plugins maven-javadoc-plugin diff --git a/xjx-sax/pom.xml b/xjx-sax/pom.xml index 2ae09e6..c86610b 100644 --- a/xjx-sax/pom.xml +++ b/xjx-sax/pom.xml @@ -4,7 +4,7 @@ io.jonasg xjx - 0.2.0 + 0.1.0 xjx-sax diff --git a/xjx-serdes/pom.xml b/xjx-serdes/pom.xml index ff53b98..5a55cb6 100644 --- a/xjx-serdes/pom.xml +++ b/xjx-serdes/pom.xml @@ -4,7 +4,7 @@ io.jonasg xjx - 0.2.0 + 0.1.0 xjx-serdes diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/Path.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Path.java similarity index 97% rename from xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/Path.java rename to xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Path.java index a431d96..18f9169 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/Path.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Path.java @@ -1,6 +1,4 @@ -package io.jonasg.xjx.serdes.deserialize; - -import io.jonasg.xjx.serdes.Section; +package io.jonasg.xjx.serdes; import java.util.Arrays; import java.util.Iterator; diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Section.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Section.java index 42639cd..3a599b6 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Section.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Section.java @@ -1,35 +1,4 @@ package io.jonasg.xjx.serdes; -import java.util.StringJoiner; - -public class Section { - - private final String name; - private final boolean isLeaf; - - public Section(String name) { - this.name = name; - isLeaf = false; - } - - public Section(String name, boolean isLeaf) { - this.name = name; - this.isLeaf = isLeaf; - } - - public String name() { - return name; - } - - public boolean isLeaf() { - return isLeaf; - } - - @Override - public String toString() { - return new StringJoiner(", ", Section.class.getSimpleName() + "[", "]") - .add("name='" + name + "'") - .add("isLeaf=" + isLeaf) - .toString(); - } +public record Section(String name, boolean isLeaf) { } diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Tag.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Tag.java index f73f6a3..ccb2d93 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Tag.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/Tag.java @@ -5,10 +5,64 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * The {@code Tag} annotation is used to mark a field for XML serialization and deserialization. + * It provides information about the XML path and optional attributes to be used during serialization and deserialization. + * + *

Example XML document:

+ *
{@code
+ * 
+ *     Product 1
+ *     Product 2
+ *     Product 3
+ * 
+ * }
+ * + *

Example Usage:

+ *
{@code
+ * @Tag(path = "/Products", items = "Name")
+ * List productNames;
+ * }
+ * In this example, the {@code List} field 'productNames' will be serialized to and deserialized from the XML path "/Products/Name". + * + *

Example XML for Serialization:

+ *
{@code
+ * 
+ *     Product 1
+ *     Product 2
+ *     Product 3
+ * 
+ * }
+ * In this example, when the {@code List} field 'productNames' is serialized, the generated XML will look like the above representation. + * + *

Annotation Usage:

+ *
    + *
  • {@code path}: Specifies the Path expression indicating the location of the XML data for serialization and deserialization.
  • + *
  • {@code attribute}: Specifies the name of an XML attribute to be used during serialization and deserialization (optional).
  • + *
  • {@code items}: Specifies additional information for serializing and deserializing items within a collection (optional).
  • + *
+ */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD}) public @interface Tag { + /** + * Specifies the Path expression indicating the location of the XML data for serialization and deserialization. + * + * @return The Path expression representing the location of the XML data. + */ String path(); + /** + * Specifies the name of an XML attribute to be used during serialization and deserialization (optional). + * + * @return The name of the XML attribute. + */ String attribute() default ""; + + /** + * Specifies additional information for serializing and deserializing items within a collection (optional). + * + * @return Additional information for serializing and deserializing items. + */ + String items() default ""; } diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/TypeMappers.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/TypeMappers.java new file mode 100644 index 0000000..9d5e2ca --- /dev/null +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/TypeMappers.java @@ -0,0 +1,89 @@ +package io.jonasg.xjx.serdes; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +public final class TypeMappers { + + static List> DOUBLE_TYPES = List.of(double.class, Double.class); + static List> LONG_TYPES = List.of(long.class, Long.class); + static List> CHAR_TYPES = List.of(char.class, Character.class); + static List> BOOLEAN_TYPES = List.of(boolean.class, Boolean.class); + + public static Set> TYPES; + + static { + TYPES = new HashSet<>(); + TYPES.addAll(DOUBLE_TYPES); + TYPES.addAll(LONG_TYPES); + TYPES.addAll(CHAR_TYPES); + TYPES.addAll(BOOLEAN_TYPES); + TYPES.add(String.class); + TYPES.add(LocalDate.class); + } + + public static Function forType(Class type) { + Function mapper = Function.identity(); + if (type.equals(String.class)) { + mapper = String::valueOf; + } + if (type.equals(Integer.class)) { + mapper = value -> Integer.parseInt(String.valueOf(value)); + } + if (LONG_TYPES.contains(type)) { + mapper = value -> Long.parseLong(String.valueOf(value)); + } + if (type.equals(BigDecimal.class)) { + mapper = value -> new BigDecimal(String.valueOf(value)); + } + if (DOUBLE_TYPES.contains(type)) { + mapper = value -> Double.valueOf(String.valueOf(value)); + } + if (CHAR_TYPES.contains(type)) { + mapper = value -> String.valueOf(value).charAt(0); + } + if (BOOLEAN_TYPES.contains(type)) { + mapper = value -> { + String lowered = String.valueOf(value).toLowerCase(); + if (lowered.equals("true") || lowered.equals("yes") || lowered.equals("1")) { + return true; + } + return false; + }; + } + if (type.equals(LocalDate.class)) { + mapper = value -> LocalDate.parse(String.valueOf(value)); + } + if (type.equals(LocalDateTime.class)) { + mapper = value -> LocalDateTime.parse(String.valueOf(value)); + } + if (type.equals(ZonedDateTime.class)) { + mapper = value -> ZonedDateTime.parse(String.valueOf(value)); + } + if (type.isEnum()) { + mapper = value -> toEnum(type, String.valueOf(value)); + } + return mapper; + } + + @SuppressWarnings("unchecked") + private static > T toEnum(Class type, String value) { + try { + T[] enumConstants = (T[]) type.getEnumConstants(); + for (T constant : enumConstants) { + if (value.equals(constant.name())) { + return constant; + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return null; + } +} diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XjxSerdes.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XjxSerdes.java index 4b95216..2f796fb 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XjxSerdes.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XjxSerdes.java @@ -6,6 +6,8 @@ import io.jonasg.xjx.serdes.deserialize.PathBasedSaxHandler; import io.jonasg.xjx.serdes.deserialize.PathWriterIndexFactory; import io.jonasg.xjx.serdes.deserialize.XjxDeserializationException; +import io.jonasg.xjx.serdes.seraialize.XmlNodeStructureFactory; +import io.jonasg.xjx.serdes.seraialize.XmlStringBuilder; import java.io.Reader; import java.io.StringReader; diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java index c1049b9..f992917 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java @@ -2,6 +2,7 @@ import io.jonasg.xjx.sax.Attribute; import io.jonasg.xjx.sax.SaxHandler; +import io.jonasg.xjx.serdes.Path; import java.util.HashMap; import java.util.LinkedList; diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java index 5e81ff3..b7b1820 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java @@ -1,9 +1,10 @@ package io.jonasg.xjx.serdes.deserialize; +import io.jonasg.xjx.serdes.Path; import io.jonasg.xjx.serdes.Tag; +import io.jonasg.xjx.serdes.TypeMappers; import io.jonasg.xjx.serdes.deserialize.accessor.FieldAccessor; import io.jonasg.xjx.serdes.reflector.FieldReflector; -import io.jonasg.xjx.serdes.reflector.Reflector; import io.jonasg.xjx.serdes.reflector.TypeReflector; import java.lang.reflect.Field; @@ -42,7 +43,10 @@ private Map buildIndex(Class type, Path path) { return doBuildIndex(type, path, index, () -> root); } - private Map doBuildIndex(Class type, Path path, Map index, Supplier root) { + private Map doBuildIndex(Class type, + Path path, + Map index, + Supplier root) { TypeReflector.reflect(type).fields() .forEach(field -> indexField(field, index, path, root)); return index; @@ -73,7 +77,10 @@ private void indexMapType(FieldReflector field, Map index, Pat } } - private static void doIndexMapType(FieldReflector field, Map index, Supplier parent, Path pathForField) { + private static void doIndexMapType(FieldReflector field, + Map index, + Supplier parent, + Path pathForField) { index.put(pathForField, PathWriter.objectInitializer(() -> { Map map = new HashMap<>(); Class valueType = (Class) ((ParameterizedType) field.genericType()).getActualTypeArguments()[1]; @@ -86,7 +93,10 @@ private static void doIndexMapType(FieldReflector field, Map i })); } - private static void indexMapAsRootType(FieldReflector field, Map index, Supplier parent, Path pathForField) { + private static void indexMapAsRootType(FieldReflector field, + Map index, + Supplier parent, + Path pathForField) { index.put(pathForField, PathWriter.rootInitializer(() -> { Map map = new HashMap<>(); FieldAccessor.of(field, parent.get()).set(map); @@ -94,7 +104,10 @@ private static void indexMapAsRootType(FieldReflector field, Map index, Path path, Supplier parent) { + private void indexComplexType(FieldReflector field, + Map index, + Path path, + Supplier parent) { if (field.hasAnnotation(Tag.class)) { doIndexComplexType(field, index, path, parent); } else { @@ -176,34 +189,49 @@ private void indexSimpleType(FieldReflector field, Map index, } private void indexSetType(FieldReflector field, Map index, Path parentPath, Supplier parent) { +// Collection set = new HashSet<>(); +// Path path = getPathForField(field, parentPath); +// var pathWriter = PathWriter.objectInitializer(() -> { +// FieldAccessor.of(field, parent.get()).set(set); +// return set; +// }); +// if (parentPath.isRoot()) { +// pathWriter.setRootInitializer(() -> { +// FieldAccessor.of(field, parent.get()).set(set); +// return parent.get(); +// }); +// } +// index.put(path, pathWriter); +// Type actualTypeArgument = ((ParameterizedType) field.genericType()).getActualTypeArguments()[0]; +// Class typeArgument = (Class) actualTypeArgument; +// var tag = Reflector.reflect(typeArgument).annotation(Tag.class); +// if (tag != null) { +// Supplier listTypeInstanceSupplier = collectionSupplierForType(typeArgument); +// index.put(Path.parse(tag.path()), PathWriter.objectInitializer(() -> { +// collectionCacheType.clear(); +// Object listTypeInstance = listTypeInstanceSupplier.get(); +// set.add(listTypeInstance); +// return listTypeInstance; +// })); +// doBuildIndex(typeArgument, Path.parse(tag.path()), index, listTypeInstanceSupplier); +// } else { +// throw new XjxDeserializationException("Generics of type Set require @Tag pointing to mapped XML path (" + typeArgument.getSimpleName() + ")"); +// } Collection set = new HashSet<>(); Path path = getPathForField(field, parentPath); var pathWriter = PathWriter.objectInitializer(() -> { FieldAccessor.of(field, parent.get()).set(set); return set; }); - if (parentPath.isRoot()) { + if (path.isRoot()) { pathWriter.setRootInitializer(() -> { FieldAccessor.of(field, parent.get()).set(set); return parent.get(); }); } index.put(path, pathWriter); - Type actualTypeArgument = ((ParameterizedType) field.genericType()).getActualTypeArguments()[0]; - Class typeArgument = (Class) actualTypeArgument; - var tag = Reflector.reflect(typeArgument).annotation(Tag.class); - if (tag != null) { - Supplier listTypeInstanceSupplier = collectionSupplierForType(typeArgument); - index.put(Path.parse(tag.path()), PathWriter.objectInitializer(() -> { - collectionCacheType.clear(); - Object listTypeInstance = listTypeInstanceSupplier.get(); - set.add(listTypeInstance); - return listTypeInstance; - })); - doBuildIndex(typeArgument, Path.parse(tag.path()), index, listTypeInstanceSupplier); - } else { - throw new XjxDeserializationException("Generics of type Set require @Tag pointing to mapped XML path (" + typeArgument.getSimpleName() + ")"); - } + + indexListTypeArgument(path, field, index, set); } private Supplier collectionSupplierForType(Class typeArgument) { @@ -231,23 +259,45 @@ private void indexListType(FieldReflector field, Map index, Pa }); } index.put(path, pathWriter); + + indexListTypeArgument(path, field, index, list); + } + + private void indexListTypeArgument(Path path, FieldReflector field, Map index, Collection list) { Type actualTypeArgument = ((ParameterizedType) field.genericType()).getActualTypeArguments()[0]; Class typeArgument = (Class) actualTypeArgument; - var tag = Reflector.reflect(typeArgument).annotation(Tag.class); - if (tag != null) { - Supplier listTypeInstanceSupplier = collectionSupplierForType(typeArgument); - index.put(Path.parse(tag.path()), PathWriter.objectInitializer(() -> { - collectionCacheType.clear(); - Object listTypeInstance = listTypeInstanceSupplier.get(); - list.add(listTypeInstance); - return listTypeInstance; - })); - doBuildIndex(typeArgument, Path.parse(tag.path()), index, listTypeInstanceSupplier); + if (TypeMappers.TYPES.contains(typeArgument)) { + indexSimpleTypeListTypeArgument(path, index, list, field, typeArgument); } else { - throw new XjxDeserializationException("Generics of type List require @Tag pointing to mapped XML path (" + typeArgument.getSimpleName() + ")"); + indexComplexListTypeArgument(index, list, typeArgument, field); } } + private static void indexSimpleTypeListTypeArgument(Path path, Map index, Collection list, FieldReflector field, Class typeArgument) { + Tag tag = field.getAnnotation(Tag.class); + index.put(path.append(Path.parse(tag.items())), + PathWriter.valueInitializer((o) -> list.add(TypeMappers.forType(typeArgument).apply(o)))); + } + + private void indexComplexListTypeArgument(Map index, Collection list, Class typeArgument, FieldReflector field) { + Supplier listTypeInstanceSupplier = collectionSupplierForType(typeArgument); + Tag tag = field.getAnnotation(Tag.class); + if (tag.items().isBlank()) { + throw new XjxDeserializationException( + """ + Field (%s) requires @Tag to have items parameter describing\ + the tag name of a single repeated tag""".formatted(typeArgument.getSimpleName(), field.name())); + } + Path path = Path.parse(tag.path()).append(Path.parse(tag.items())); + index.put(path, PathWriter.objectInitializer(() -> { + collectionCacheType.clear(); + Object listTypeInstance = listTypeInstanceSupplier.get(); + list.add(listTypeInstance); + return listTypeInstance; + })); + doBuildIndex(typeArgument, path, index, listTypeInstanceSupplier); + } + private Path getPathForField(FieldReflector field, Path path) { Tag tag = field.getAnnotation(Tag.class); if (tag != null) { diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/accessor/FieldAccessor.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/accessor/FieldAccessor.java index d51e6f9..dc1603c 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/accessor/FieldAccessor.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/accessor/FieldAccessor.java @@ -1,83 +1,19 @@ package io.jonasg.xjx.serdes.deserialize.accessor; +import io.jonasg.xjx.serdes.TypeMappers; import io.jonasg.xjx.serdes.reflector.FieldReflector; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.function.Function; - public interface FieldAccessor { - List> DOUBLE_TYPES = List.of(double.class, Double.class); - List> LONG_TYPES = List.of(long.class, Long.class); - List> CHAR_TYPES = List.of(char.class, Character.class); - List> BOOLEAN_TYPES = List.of(boolean.class, Boolean.class); - static FieldAccessor of(FieldReflector field, Object instance) { var setterFieldAccessor = new SetterFieldAccessor(field, instance); //TODO optimize if (setterFieldAccessor.hasSetterForField()) { return new SetterFieldAccessor(field, instance); } - Function mapper = Function.identity(); - if (field.type().equals(String.class)) { - mapper = String::valueOf; - } - if (field.type().equals(Integer.class)) { - mapper = value -> Integer.parseInt(String.valueOf(value)); - } - if (LONG_TYPES.contains(field.type())) { - mapper = value -> Long.parseLong(String.valueOf(value)); - } - if (field.type().equals(BigDecimal.class)) { - mapper = value -> new BigDecimal(String.valueOf(value)); - } - if (DOUBLE_TYPES.contains(field.type())) { - mapper = value -> Double.valueOf(String.valueOf(value)); - } - if (CHAR_TYPES.contains(field.type())) { - mapper = value -> String.valueOf(value).charAt(0); - } - if (BOOLEAN_TYPES.contains(field.type())) { - mapper = value -> { - String lowered = String.valueOf(value).toLowerCase(); - if (lowered.equals("true") || lowered.equals("yes") || lowered.equals("1")) { - return true; - } - return false; - }; - } - if (field.type().equals(LocalDate.class)) { - mapper = value -> LocalDate.parse(String.valueOf(value)); - } - if (field.type().equals(LocalDateTime.class)) { - mapper = value -> LocalDateTime.parse(String.valueOf(value)); - } - if (field.type().equals(ZonedDateTime.class)) { - mapper = value -> ZonedDateTime.parse(String.valueOf(value)); - } - if (field.type().isEnum()) { - mapper = value -> toEnum(field.type(), String.valueOf(value)); - } + var mapper = TypeMappers.forType(field.type()); return new ReflectiveFieldAccessor(field, instance, mapper); } void set(Object value); - @SuppressWarnings("unchecked") - private static > T toEnum(Class type, String value) { - try { - T[] enumConstants = (T[]) type.getEnumConstants(); - for (T constant : enumConstants) { - if (value.equals(constant.name())) { - return constant; - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - return null; - } } diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XmlNodeStructureFactory.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/seraialize/XmlNodeStructureFactory.java similarity index 91% rename from xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XmlNodeStructureFactory.java rename to xjx-serdes/src/main/java/io/jonasg/xjx/serdes/seraialize/XmlNodeStructureFactory.java index ed59ac6..7b5ab18 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XmlNodeStructureFactory.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/seraialize/XmlNodeStructureFactory.java @@ -1,9 +1,10 @@ -package io.jonasg.xjx.serdes; +package io.jonasg.xjx.serdes.seraialize; -import io.jonasg.xjx.serdes.deserialize.Path; +import io.jonasg.xjx.serdes.Section; +import io.jonasg.xjx.serdes.Tag; +import io.jonasg.xjx.serdes.Path; import io.jonasg.xjx.serdes.reflector.InstanceField; import io.jonasg.xjx.serdes.reflector.Reflector; -import io.jonasg.xjx.serdes.seraialize.XmlNode; public class XmlNodeStructureFactory { diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XmlStringBuilder.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/seraialize/XmlStringBuilder.java similarity index 97% rename from xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XmlStringBuilder.java rename to xjx-serdes/src/main/java/io/jonasg/xjx/serdes/seraialize/XmlStringBuilder.java index 23e6bb0..e8e7d28 100644 --- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XmlStringBuilder.java +++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/seraialize/XmlStringBuilder.java @@ -1,4 +1,4 @@ -package io.jonasg.xjx.serdes; +package io.jonasg.xjx.serdes.seraialize; import io.jonasg.xjx.serdes.seraialize.XmlNode; diff --git a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/ListDeserializationTest.java b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/ListDeserializationTest.java index 1df967c..7118cc7 100644 --- a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/ListDeserializationTest.java +++ b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/ListDeserializationTest.java @@ -2,16 +2,161 @@ import io.jonasg.xjx.serdes.Tag; import io.jonasg.xjx.serdes.XjxSerdes; -import org.assertj.core.api.Assertions; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.Test; +import java.time.LocalDate; import java.util.List; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ListDeserializationTest { + @Test + void deserializeIntoListField_OfStringType() { + // given + String data = """ + + + + 2023-09-12 + 2023-09-13 + 2023-09-14 + 2023-09-15 + + + """; + + // when + var dataHolder = new XjxSerdes().read(data, ListOfStrings.class); + + // then + assertThat(dataHolder.strings).containsExactlyInAnyOrder( + "2023-09-12", + "2023-09-13", + "2023-09-14", + "2023-09-15" + ); + } + + static class ListOfStrings { + @Tag(path = "/Data/Strings", items = "String") + List strings; + } + + @Test + void deserializeIntoListField_OfBooleanType() { + // given + String data = """ + + + + True + true + yes + YeS + 1 + + + """; + + // when + var weatherData = new XjxSerdes().read(data, ListOfBooleans.class); + + // then + assertThat(weatherData.booleans).containsOnly(Boolean.TRUE); + } + + static class ListOfBooleans { + @Tag(path = "/Data/Booleans", items = "Boolean") + List booleans; + } + + + @Test + void deserializeIntoListField_OfLongType() { + // given + String data = """ + + + + 123456789 + -987654321 + 0 + + + """; + + // when + var listOfLongs = new XjxSerdes().read(data, ListOfLongs.class); + + // then + assertThat(listOfLongs.longs).containsExactly(123456789L, -987654321L, 0L); + } + + static class ListOfLongs { + @Tag(path = "/Data/Longs", items = "Long") + List longs; + } + + @Test + void deserializeIntoListField_OfDoubleType() { + // given + String data = """ + + + + 3.14 + -2.5 + 0.0 + + + """; + + // when + var listOfDoubles = new XjxSerdes().read(data, ListOfDoubles.class); + + // then + assertThat(listOfDoubles.doubles).containsExactly(3.14, -2.5, 0.0); + } + + static class ListOfDoubles { + @Tag(path = "/Data/Doubles", items = "Double") + List doubles; + } + + + @Test + void deserializeIntoListField_OfLocalDate() { + // given + String data = """ + + + + 2024-01-01 + 2024-02-01 + 2024-03-01 + + + """; + + // when + var listOfDates = new XjxSerdes().read(data, ListOfDates.class); + + // then + assertThat(listOfDates.dates).containsExactly( + LocalDate.of(2024, 1, 1), + LocalDate.of(2024, 2, 1), + LocalDate.of(2024, 3, 1) + ); + } + + static class ListOfDates { + @Tag(path = "/Data/LocalDates", items = "LocalDate") + List dates; + } + @Test void deserializeIntoListField_OfComplexType_ContainingTopLevelMapping() { // given @@ -58,11 +203,10 @@ public static class WeatherData { public WeatherData() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") List forecasts; } - @Tag(path = "/WeatherData/Forecasts/Day") public static class Forecast { public Forecast() { } @@ -75,42 +219,42 @@ public Forecast() { void deserializeIntoListField_OfComplexType_ContainingComplexTypesWithCustomMapping() { // given String data = """ - - - - - - 71 - °F - - - 60 - °F - - - 10 - % - - Partly Cloudy - - - - 78 - °F - - - 62 - °F - - - 12 - % - - Partly Cloudy - - - - """; + + + + + + 71 + °F + + + 60 + °F + + + 10 + % + + Partly Cloudy + + + + 78 + °F + + + 62 + °F + + + 12 + % + + Partly Cloudy + + + + """; // when PrecipitationData precipitationData = new XjxSerdes().read(data, PrecipitationData.class); @@ -125,34 +269,34 @@ void deserializeIntoListField_OfComplexType_ContainingComplexTypesWithCustomMapp void deserializeIntoListField_EvenIfNoneOfTheInnerMappedFieldsOfComplexTypeCanBeMapped() { // given String data = """ - - - - - - 71 - °F - - - 60 - °F - - Partly Cloudy - - - - 78 - °F - - - 62 - °F - - Partly Cloudy - - - - """; + + + + + + 71 + °F + + + 60 + °F + + Partly Cloudy + + + + 78 + °F + + + 62 + °F + + Partly Cloudy + + + + """; // when PrecipitationData precipitationData = new XjxSerdes().read(data, PrecipitationData.class); @@ -165,11 +309,11 @@ void deserializeIntoListField_EvenIfNoneOfTheInnerMappedFieldsOfComplexTypeCanBe void listsMappedOntoSelfClosingTag_containsEmptyList() { // given String data = """ - - - - - """; + + + + + """; // when PrecipitationData precipitationData = new XjxSerdes().read(data, PrecipitationData.class); @@ -183,12 +327,11 @@ static class PrecipitationData { public PrecipitationData() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") List precipitations; } - @Tag(path = "/WeatherData/Forecasts/Day") static class Precipitation { public Precipitation() { @@ -208,7 +351,7 @@ public PrecipitationValue() { } @Test - void informUserThatAList_itsGenericType_shouldBeAnnotatedWithTag() { + void informUserThatAMappedListField_shouldHaveAFilledInItemsParameter() { // given String data = """ @@ -245,9 +388,10 @@ void informUserThatAList_itsGenericType_shouldBeAnnotatedWithTag() { // then assertThatThrownBy(when) - .hasMessage("Generics of type List require @Tag pointing to mapped XML path (ForecastWithMissingTag)"); + .hasMessage("Field (ForecastWithMissingTag) requires @Tag to have items parameter describing the tag name of a single repeated tag"); } + @SuppressWarnings("unused") public static class WeatherDataWithMissingTag { public WeatherDataWithMissingTag() { } @@ -256,6 +400,7 @@ public WeatherDataWithMissingTag() { List forecasts; } + @SuppressWarnings("unused") public static class ForecastWithMissingTag { public ForecastWithMissingTag() { } @@ -310,11 +455,10 @@ public static class WeatherDataRelativeMapping { public WeatherDataRelativeMapping() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") List forecasts; } - @Tag(path = "/WeatherData/Forecasts/Day") public static class ForecastRelativeMapping { public ForecastRelativeMapping() { } @@ -371,11 +515,10 @@ public static class WeatherDataRelativeAndAbsoluteMapping { public WeatherDataRelativeAndAbsoluteMapping() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") List forecasts; } - @Tag(path = "/WeatherData/Forecasts/Day") public static class ForecastRelativeAndAbsoluteMapping { public ForecastRelativeAndAbsoluteMapping() { } @@ -430,11 +573,10 @@ static class Gpx { public Gpx() { } - @Tag(path = "/gpx") + @Tag(path = "/gpx", items = "wpt") List wayPoints; } - @Tag(path = "/gpx/wpt") static class Wpt { public Wpt() { } diff --git a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/SetDeserializationTest.java b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/SetDeserializationTest.java index 229852b..f267170 100644 --- a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/SetDeserializationTest.java +++ b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/deserialize/SetDeserializationTest.java @@ -12,6 +12,40 @@ import static org.assertj.core.api.Assertions.assertThat; public class SetDeserializationTest { + + @Test + void deserializeIntoListField_OfStringType() { + // given + String data = """ + + + + 2023-09-12 + 2023-09-13 + 2023-09-14 + 2023-09-15 + + + """; + + // when + var dataHolder = new XjxSerdes().read(data, ListOfStrings.class); + + // then + assertThat(dataHolder.strings).containsExactlyInAnyOrder( + "2023-09-12", + "2023-09-13", + "2023-09-14", + "2023-09-15" + ); + } + + static class ListOfStrings { + @Tag(path = "/Data/Strings", items = "String") + Set strings; + } + + @Test void deserializeIntoSetField_OfComplexType_ContainingTopLevelMapping() { // given @@ -57,14 +91,13 @@ void deserializeIntoSetField_OfComplexType_ContainingTopLevelMapping() { } public static class WeatherData { - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") Set forecasts; public WeatherData() { } } - @Tag(path = "/WeatherData/Forecasts/Day") public static class Forecast { @Tag(path = "/WeatherData/Forecasts/Day/High/Value") @@ -150,12 +183,11 @@ static class PrecipitationData { public PrecipitationData() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") Set precipitations; } - @Tag(path = "/WeatherData/Forecasts/Day") static class Precipitation { PrecipitationValue precipitationValue; @@ -212,7 +244,7 @@ public int hashCode() { } @Test - void informUserThatASet_itsGenericType_shouldBeAnnotatedWithTag() { + void informUserThatFieldOfTypeSet_shouldHaveItemsFilledIn() { // given String data = """ @@ -249,7 +281,7 @@ void informUserThatASet_itsGenericType_shouldBeAnnotatedWithTag() { // then Assertions.assertThatThrownBy(when) - .hasMessage("Generics of type Set require @Tag pointing to mapped XML path (ForecastWithMissingTag)"); + .hasMessage("Field (ForecastWithMissingTag) requires @Tag to have items parameter describing the tag name of a single repeated tag"); } public static class WeatherDataWithMissingTag { @@ -314,7 +346,7 @@ public static class WeatherDataRelativeMapping { public WeatherDataRelativeMapping() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") Set forecasts; } @@ -373,7 +405,7 @@ public static class WeatherDataRelativeAndAbsoluteMapping { public WeatherDataRelativeAndAbsoluteMapping() { } - @Tag(path = "/WeatherData/Forecasts") + @Tag(path = "/WeatherData/Forecasts", items = "Day") Set forecasts; } @@ -430,7 +462,7 @@ static class Gpx { public Gpx() { } - @Tag(path = "/gpx") + @Tag(path = "/gpx", items = "wpt") Set wayPoints; } @@ -445,5 +477,4 @@ public Wpt() { @Tag(path = "time") String time; } - } diff --git a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/seraialize/GeneralSerializationTest.java b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/serialize/GeneralSerializationTest.java similarity index 98% rename from xjx-serdes/src/test/java/io/jonasg/xjx/serdes/seraialize/GeneralSerializationTest.java rename to xjx-serdes/src/test/java/io/jonasg/xjx/serdes/serialize/GeneralSerializationTest.java index e806283..4476378 100644 --- a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/seraialize/GeneralSerializationTest.java +++ b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/serialize/GeneralSerializationTest.java @@ -1,4 +1,4 @@ -package io.jonasg.xjx.serdes.seraialize; +package io.jonasg.xjx.serdes.serialize; import io.jonasg.xjx.serdes.Tag; import io.jonasg.xjx.serdes.XjxSerdes; diff --git a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/seraialize/TagAttributeSerializationTest.java b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/serialize/TagAttributeSerializationTest.java similarity index 99% rename from xjx-serdes/src/test/java/io/jonasg/xjx/serdes/seraialize/TagAttributeSerializationTest.java rename to xjx-serdes/src/test/java/io/jonasg/xjx/serdes/serialize/TagAttributeSerializationTest.java index 9f1249b..35051cb 100644 --- a/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/seraialize/TagAttributeSerializationTest.java +++ b/xjx-serdes/src/test/java/io/jonasg/xjx/serdes/serialize/TagAttributeSerializationTest.java @@ -1,4 +1,4 @@ -package io.jonasg.xjx.serdes.seraialize; +package io.jonasg.xjx.serdes.serialize; import io.jonasg.xjx.serdes.Tag; import io.jonasg.xjx.serdes.XjxSerdes;