diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index de522b67..e3d86f3c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -48,6 +48,9 @@ jobs:
run: ./mvnw install -e --ntp -B
- name: Test JPMS
run: ./mvnw clean verify -pl it/java8 -P it-jpms -e --ntp -B
+ - name: Test Maven 3.2.5 compatibility
+ working-directory: maven-plugin/it
+ run: ./mvnw stype:gen -e -B
- name: Javadoc
run: ./mvnw -P release javadoc:javadoc --ntp -B
- name: Upload generated sources
@@ -93,6 +96,9 @@ jobs:
key: build_maven
- name: Test
run: ./mvnw verify -pl it/java8 -e --ntp -B
+ - name: Maven Plugin Test
+ working-directory: maven-plugin/it
+ run: ./mvnw stype:gen -e -B
client_test:
needs: [build_and_test]
diff --git a/README.md b/README.md
index 4ed7bc43..4072f4ea 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://github.com/cuzfrog/sharedtype/actions/workflows/ci.yaml)
[](https://central.sonatype.com/search?q=g:online.sharedtype++a:sharedtype&smo=true)
-# SharedType - Sharing Java Types made easy
+# SharedType - Lightweight Java Type Sharing
From Java:
```java
@SharedType
@@ -36,13 +36,11 @@ pub struct User {
```
## Features
-* Java8 compatible; Java 9 module (Jigsaw) compatible.
+* Java8+ compatible.
* Generics support.
* Compile-time constant support.
-* Client source dependency is only `@SharedType` retained at source code level.
-* SharedType annotation processor has only 1 dependency: [mustache](https://github.com/spullara/mustache.java).
-* Parsing takes milliseconds with `-proc:only`.
-* Intuitive defaults, put `@SharedType` and there you go. Global + class level options.
+* Fast. (Execution takes milliseconds with `-proc:only`.)
+* Simple global + type level configurations.
## Documentation
* [User Guide](doc/Usage.md)
diff --git a/annotation/src/main/java/online/sharedtype/SharedType.java b/annotation/src/main/java/online/sharedtype/SharedType.java
index e244b613..9f71876b 100644
--- a/annotation/src/main/java/online/sharedtype/SharedType.java
+++ b/annotation/src/main/java/online/sharedtype/SharedType.java
@@ -157,8 +157,7 @@
* Custom code snippet:
* Clients can provide custom code snippets to be injected into the emitted file.
* This can be useful when e.g. a 3rd party type is referenced at client code.
- * By default, SharedType will search files "sharedtype-custom-code.ts", "sharedtype-custom-code.rs" respectively on cmd path.
- * File paths can be configured via global properties.
+ * Custom code file paths can be configured via global properties.
*
*
*
diff --git a/doc/Development.md b/doc/Development.md
index 3e98b25a..39cef68e 100644
--- a/doc/Development.md
+++ b/doc/Development.md
@@ -14,6 +14,7 @@ Internal types also have javadoc for more information.
* `java17` uses symlink to reuse types in `java8` then does more type checks, e.g. for Java `record`.
* `client-test` contains target languages' tests respectively against generated code.
* `e2e` contains e2e json 2-way serialization and deserialization tests against target languages' http servers.
+* `maven-plugin` contains maven plugin for SharedType annotation, and `maven-plugin/it` contains integration tests for maven plugin.
Domain types are shared among processor and integration tests to reduce maven module count.
@@ -83,6 +84,12 @@ Compile specific classes, **along with debug, this is useful for developing a sp
./mvnw clean compile -pl it/java17 -DcompileClasses=online/sharedtype/it/java8/TempClass.java
```
+### Maven Plugin Development
+Debug Maven plugin IT:
+```bash
+mvnd -pl maven-plugin clean install && ./mvne -pl maven-plugin/it sharedtype:gen
+```
+
## Coding Style Guide / Keep it simple
1. since annotation processing is one shot execution, JIT is not likely to optimize the code. So prefer plain loop than long calling stacks like Stream chains.
2. no adding dependencies without strong justification.
diff --git a/doc/Usage.md b/doc/Usage.md
index 3d47d304..9d9830b4 100644
--- a/doc/Usage.md
+++ b/doc/Usage.md
@@ -5,51 +5,38 @@ Menu:
* [Configurations](#Configurations)
## Setup
-
### Maven
-Add sharedtype dependency, the annotation `@SharedType` is only used at compile time on source code:
-```xml
-
- online.sharedtype
- sharedtype
- ${sharedtype.version}
- provided
- true
-
-```
-
Add Maven properties:
```xml
-
- 0.12.1
- false
+ 0.13.0
```
-Setup annotation processing:
+
+Add sharedtype-maven-plugin:
```xml
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- online.sharedtype
- sharedtype-ap
- ${sharedtype.version}
-
-
- true
-
-
- ${compilerArg}
- -Asharedtype.enabled=${sharedtype.enabled}
-
-
+ online.sharedtype
+ sharedtype-maven-plugin
+ ${project.version}
```
+Add sharedtype dependency:
+```xml
+
+ online.sharedtype
+ sharedtype
+ ${sharedtype.version}
+ provided
+ true
+
+```
+
+`sharedtype-maven-plugin` requires Maven 3.2.5+.
+Annotation processing can also be setup and configured via `maven-compiler-plugin`, see [example](../it/pom.xml).
+The advantage of using `sharedtype-maven-plugin` is that no need to execute other annotation processors if there are multiple.
## Usage
### A simple example
@@ -59,8 +46,8 @@ Annotate on a class:
record User(String name, int age, String email) {}
```
-Execute annotation processing:
-* maven: `./mvnw compile -DcompilerArg=-proc:only -Dsharedtype.enabled=true`
+Execute:
+* maven: `./mvnw stype:gen` (Why `stype`? Because it's easy to type while explicitly enough to remember.)
By default, below code will be generated:
```typescript
@@ -75,25 +62,29 @@ export interface User {
#### Global options
By default, the file `sharedtype.properties` on current cmd path will be picked up.
-You can customize the path by config `maven-compiler-plugin`:
+You can customize the path:
```xml
-
- -Asharedtype.propsFile=${your.properties.path}
-
+
+
+ ${project.basedir}/sharedtype-my-custom.properties
+
+
```
-Properties can also be passed in as system properties, which will override the properties files, e.g.
-```bash
-./mvnw clean compile -Dsharedtype.typescript.custom-code-path=it/custom-code.ts
-```
-or
-```bash
-MAVEN_OPTS="-Dsharedtype.typescript.custom-code-path=it/custom-code.ts" ./mvnw clean compile
+You can also specify individual properties:
+```xml
+
+
+
+ ${project.basedir}/custom-code.ts
+
+
+
```
-or can use [properties-maven-plugin](https://www.mojohaus.org/properties-maven-plugin/usage.html#set-system-properties) to set system properties for the build.
-See [Default Properties](../processor/src/main/resources/sharedtype-default.properties) for details.
+See [Default Properties](../processor/src/main/resources/sharedtype-default.properties) for all property entries.
+Execution goal `gen` can be bound to a Maven lifecycle phase.
#### Per annotation options
See Javadoc on [@SharedType](../annotation/src/main/java/online/sharedtype/SharedType.java) for details.
diff --git a/it/pom.xml b/it/pom.xml
index ebc0d744..cd285da5 100644
--- a/it/pom.xml
+++ b/it/pom.xml
@@ -1,5 +1,6 @@
-
+
4.0.0
online.sharedtype
@@ -13,7 +14,7 @@
pom
-
+
it/sharedtype.properties
@@ -99,39 +100,12 @@
${compilerArg}
-Asharedtype.propsFile=${sharedtype.propsFile}
-Asharedtype.enabled=true
+
${compileClasses}
${excludeClasses}
-
- org.codehaus.mojo
- properties-maven-plugin
- 1.2.1
-
-
-
- set-system-properties
-
-
-
-
- sharedtype.typescript.custom-code-path
- it/custom-code.ts
-
-
- sharedtype.go.custom-code-path
- it/custom-code.go
-
-
- sharedtype.rust.custom-code-path
- it/custom-code.rs
-
-
-
-
-
-
@@ -174,6 +148,20 @@
+
+ add-test-resource
+ generate-test-resources
+
+ add-test-resource
+
+
+
+
+ ${project.build.directory}/generated-sources
+
+
+
+
diff --git a/it/sharedtype.properties b/it/sharedtype.properties
index c93c1bf0..921c09d4 100644
--- a/it/sharedtype.properties
+++ b/it/sharedtype.properties
@@ -10,3 +10,7 @@ sharedtype.typescript.type-mappings=online.sharedtype.it.java8.types.MyType1:Arr
sharedtype.rust.type-mappings=online.sharedtype.it.java8.types.MyType1:Vec
sharedtype.go.type-mappings=online.sharedtype.it.java8.types.MyType1:[]
+
+sharedtype.typescript.custom-code-path=it/custom-code.ts
+sharedtype.go.custom-code-path=it/custom-code.go
+sharedtype.rust.custom-code-path=it/custom-code.rs
diff --git a/maven-plugin/it/.mvn/wrapper/maven-wrapper.properties b/maven-plugin/it/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..7bf98fb4
--- /dev/null
+++ b/maven-plugin/it/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,21 @@
+# 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.
+wrapperVersion=3.3.2
+distributionType=only-script
+
+# This is for Maven compatibility test
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.2.5/apache-maven-3.2.5-bin.zip
diff --git a/maven-plugin/it/mvne b/maven-plugin/it/mvne
new file mode 100755
index 00000000..2fccba55
--- /dev/null
+++ b/maven-plugin/it/mvne
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export MAVEN_OPTS="-Xdebug -Xnoagent -Xint -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 $MAVEN_OPTS"
+./mvnw "$@"
diff --git a/maven-plugin/it/mvnw b/maven-plugin/it/mvnw
new file mode 100755
index 00000000..19529ddf
--- /dev/null
+++ b/maven-plugin/it/mvnw
@@ -0,0 +1,259 @@
+#!/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.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ 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"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/maven-plugin/it/pom.xml b/maven-plugin/it/pom.xml
new file mode 100644
index 00000000..b52431a2
--- /dev/null
+++ b/maven-plugin/it/pom.xml
@@ -0,0 +1,78 @@
+
+
+ 4.0.0
+
+ online.sharedtype
+ sharedtype-parent
+ 0.13.0-SNAPSHOT
+ ../../pom.xml
+
+
+ sharedtype-maven-plugin-it
+ SharedType Maven Plugin Integration Test
+
+
+
+ ${project.groupId}
+ sharedtype
+ ${project.version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ online.sharedtype
+ sharedtype-maven-plugin
+ ${project.version}
+
+ ${project.basedir}/sharedtype.properties
+
+ ${project.parent.basedir}/it/custom-code.ts
+
+
+
+
+ gen-for-testing
+ generate-test-resources
+
+ gen
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-resource
+ generate-test-resources
+
+ add-test-resource
+
+
+
+
+ ${project.build.directory}/generated-sources
+
+
+
+
+
+
+
+
+
diff --git a/maven-plugin/it/sharedtype.properties b/maven-plugin/it/sharedtype.properties
new file mode 100644
index 00000000..19ee8ccd
--- /dev/null
+++ b/maven-plugin/it/sharedtype.properties
@@ -0,0 +1 @@
+sharedtype.targets=TYPESCRIPT
diff --git a/maven-plugin/it/src/main/java/online/sharedtype/maven/it/MyClass.java b/maven-plugin/it/src/main/java/online/sharedtype/maven/it/MyClass.java
new file mode 100644
index 00000000..513ea98c
--- /dev/null
+++ b/maven-plugin/it/src/main/java/online/sharedtype/maven/it/MyClass.java
@@ -0,0 +1,15 @@
+package online.sharedtype.maven.it;
+
+import online.sharedtype.SharedType;
+
+import java.util.Optional;
+
+@SharedType
+final class MyClass {
+ private int value;
+}
+
+@SharedType
+final class MyClass2> {
+ private T value;
+}
diff --git a/maven-plugin/it/src/test/java/online/sharedtype/maven/it/MavenPluginIntegrationTest.java b/maven-plugin/it/src/test/java/online/sharedtype/maven/it/MavenPluginIntegrationTest.java
new file mode 100644
index 00000000..b3afbe37
--- /dev/null
+++ b/maven-plugin/it/src/test/java/online/sharedtype/maven/it/MavenPluginIntegrationTest.java
@@ -0,0 +1,26 @@
+package online.sharedtype.maven.it;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+final class MavenPluginIntegrationTest {
+ @Test
+ void verifyGeneratedFile() throws Exception {
+ var expectedContent = readResourceContent("expected-types.ts");
+ var generatedFileContent = readResourceContent("types.ts");
+
+ assertThat(generatedFileContent).isEqualTo(expectedContent);
+ }
+
+ static String readResourceContent(String path) throws Exception {
+ var resource = MavenPluginIntegrationTest.class.getClassLoader().getResource(path);
+ if (resource == null) {
+ throw new RuntimeException("Resource not found: " + path);
+ }
+ return Files.readString(Path.of(resource.toURI()));
+ }
+}
diff --git a/maven-plugin/it/src/test/resources/expected-types.ts b/maven-plugin/it/src/test/resources/expected-types.ts
new file mode 100644
index 00000000..f328f010
--- /dev/null
+++ b/maven-plugin/it/src/test/resources/expected-types.ts
@@ -0,0 +1,15 @@
+// Code generated by https://github.com/SharedType/sharedtype
+
+// test code snippet
+export interface CustomCode {
+}
+
+
+export interface MyClass {
+ readonly value: number;
+}
+
+export interface MyClass2 {
+ readonly value: T;
+}
+
diff --git a/maven-plugin/pom.xml b/maven-plugin/pom.xml
new file mode 100644
index 00000000..f5727706
--- /dev/null
+++ b/maven-plugin/pom.xml
@@ -0,0 +1,148 @@
+
+
+ 4.0.0
+
+ online.sharedtype
+ sharedtype-parent
+ 0.13.0-SNAPSHOT
+ ../pom.xml
+
+
+ sharedtype-maven-plugin
+ 0.13.0-SNAPSHOT
+ maven-plugin
+ SharedType Maven Plugin
+
+
+ 3.9.5
+ 3.15.1
+
+
+
+
+ ${project.groupId}
+ sharedtype-ap
+ ${project.version}
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${maven.version}
+ provided
+
+
+ org.apache.maven
+ maven-core
+ ${maven.version}
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ ${maven-plugin-tools.version}
+ provided
+
+
+ javax.inject
+ javax.inject
+ 1
+ provided
+
+
+
+ com.github.codeteapot.maven.plugin-testing
+ maven-plugin-testing-harness-junit-jupiter
+ 1.1.1
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit-pioneer
+ junit-pioneer
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+
+
+
+ maven-clean-plugin
+
+ true
+
+
+ ${project.build.directory}
+
+ **/*
+
+
+
+ ${settings.localRepository}/online/sharedtype/sharedtype-maven-plugin
+
+ **/*
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ ${maven-plugin-tools.version}
+
+ stype
+
+
+
+ help-mojo
+
+
+ helpmojo
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-report-plugin
+ ${maven-plugin-tools.version}
+
+
+
+
diff --git a/maven-plugin/src/main/java/online/sharedtype/maven/DependencyResolver.java b/maven-plugin/src/main/java/online/sharedtype/maven/DependencyResolver.java
new file mode 100644
index 00000000..67e3038f
--- /dev/null
+++ b/maven-plugin/src/main/java/online/sharedtype/maven/DependencyResolver.java
@@ -0,0 +1,51 @@
+package online.sharedtype.maven;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResult;
+
+import java.io.File;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Cause Chung
+ */
+final class DependencyResolver {
+ private final RepositorySystem repositorySystem;
+ private final MavenSession session;
+ private final MavenProject project;
+
+ DependencyResolver(RepositorySystem repositorySystem, MavenSession session, MavenProject project) {
+ this.repositorySystem = repositorySystem;
+ this.session = session;
+ this.project = project;
+ }
+
+ List getClasspathDependencies() throws MojoExecutionException {
+ try {
+ ArtifactTypeRegistry artifactTypeRegistry =
+ session.getRepositorySession().getArtifactTypeRegistry();
+ CollectRequest collectRequest = new CollectRequest(
+ project.getDependencies().stream().map(md -> RepositoryUtils.toDependency(md, artifactTypeRegistry)).collect(Collectors.toList()),
+ project.getDependencyManagement().getDependencies().stream()
+ .map(md -> RepositoryUtils.toDependency(md, artifactTypeRegistry)).collect(Collectors.toList()),
+ project.getRemoteProjectRepositories()
+ );
+ DependencyRequest dependencyRequest = new DependencyRequest();
+ dependencyRequest.setCollectRequest(collectRequest);
+ DependencyResult dependencyResult = repositorySystem.resolveDependencies(session.getRepositorySession(), dependencyRequest);
+ return dependencyResult.getArtifactResults().stream()
+ .map(resolved -> resolved.getArtifact().getFile())
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ throw new MojoExecutionException("Failed to resolve dependency, ", e);
+ }
+ }
+}
diff --git a/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeDiagnosticListener.java b/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeDiagnosticListener.java
new file mode 100644
index 00000000..7f0c372f
--- /dev/null
+++ b/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeDiagnosticListener.java
@@ -0,0 +1,56 @@
+package online.sharedtype.maven;
+
+import com.google.common.annotations.VisibleForTesting;
+import online.sharedtype.processor.support.annotation.SideEffect;
+import org.apache.maven.plugin.logging.Log;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+final class SharedTypeDiagnosticListener implements DiagnosticListener {
+ private final Log log;
+ private final Path projectBaseDir;
+ SharedTypeDiagnosticListener(Log log, Path projectBaseDir) {
+ this.log = log;
+ this.projectBaseDir = projectBaseDir;
+ }
+
+ @Override
+ public void report(Diagnostic extends JavaFileObject> diagnostic) {
+ StringBuilder sb = new StringBuilder();
+
+ addSourceInfo(sb, diagnostic);
+ sb.append(diagnostic.getMessage(null));
+ String message = sb.toString();
+ switch (diagnostic.getKind()) {
+ case NOTE:
+ case OTHER:
+ log.info(message);
+ break;
+ case WARNING:
+ case MANDATORY_WARNING:
+ log.warn(message);
+ break;
+ case ERROR:
+ log.error(message);
+ break;
+ }
+ }
+
+ @VisibleForTesting
+ void addSourceInfo(@SideEffect StringBuilder sb, Diagnostic extends JavaFileObject> diagnostic) {
+ if (diagnostic.getSource() == null) {
+ return;
+ }
+ JavaFileObject source = diagnostic.getSource();
+ sb.append(projectBaseDir.relativize(Paths.get(source.getName())));
+ sb.append(':');
+ sb.append(diagnostic.getLineNumber());
+ sb.append(':');
+ sb.append(diagnostic.getColumnNumber());
+ sb.append(" ");
+ }
+}
diff --git a/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeGenMojo.java b/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeGenMojo.java
new file mode 100644
index 00000000..10f1d5b5
--- /dev/null
+++ b/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeGenMojo.java
@@ -0,0 +1,158 @@
+package online.sharedtype.maven;
+
+import online.sharedtype.SharedType;
+import online.sharedtype.processor.SharedTypeAnnotationProcessor;
+import online.sharedtype.processor.support.annotation.Nullable;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+
+import javax.inject.Inject;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import java.io.File;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Generate types from {@link SharedType} annotated classes.
+ * See SharedType for details.
+ *
+ * @author Cause Chung
+ */
+@Mojo(name = "gen")
+public final class SharedTypeGenMojo extends AbstractMojo {
+ private static final String JAVA8_VERSION = "1.8";
+ private static final List DEFAULT_COMPILER_OPTIONS = Arrays.asList("-proc:only", "-Asharedtype.enabled=true");
+ private @Inject RepositorySystem repositorySystem;
+
+ @Inject
+ private MavenSession session;
+
+ @Inject
+ private MavenProject project;
+
+ /**
+ * Output directory for generated types. Defaults to '${project.build.directory}/generated-sources'.
+ */
+ @Parameter(defaultValue = "${project.build.directory}/generated-sources")
+ private String outputDirectory;
+
+ /**
+ * The path of file 'sharedtype.properties'. If not provided, default values will be used. If provided, the file must exist.
+ * User-provided properties will be checked in below order:
+ * 1. 'sharedtype.properties' file set by this config, 2. individual properties set by this plugin's 'properties' config, 3. System properties.
+ */
+ @Nullable
+ @Parameter
+ private String propertyFile;
+
+ /**
+ * Sharedtype properties. See doc for all the property entries. User-provided properties will be checked in below order:
+ * 1. 'sharedtype.properties' file, 2. this config, 3. System properties.
+ */
+ @Nullable
+ @Parameter
+ private Map properties;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ SharedTypeDiagnosticListener diagnosticListener = new SharedTypeDiagnosticListener(getLog(), project.getBasedir().toPath());
+ JavaCompiler compiler = getJavaCompiler();
+
+ DependencyResolver dependencyResolver = new DependencyResolver(repositorySystem, session, project);
+ StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, getCharset());
+ try {
+ Path outputDirPath = Paths.get(outputDirectory);
+ if (Files.notExists(outputDirPath)) {
+ Files.createDirectories(outputDirPath);
+ }
+ fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singleton(outputDirPath.toFile()));
+ fileManager.setLocation(StandardLocation.CLASS_PATH, dependencyResolver.getClasspathDependencies());
+ } catch (Exception e) {
+ throw new MojoExecutionException(e);
+ }
+ Iterable extends JavaFileObject> sources = fileManager.getJavaFileObjectsFromFiles(walkAllSourceFiles());
+
+ try (SharedTypeLogger logger = new SharedTypeLogger(getLog())) {
+ JavaCompiler.CompilationTask task = compiler.getTask(logger, fileManager, diagnosticListener, getCompilerOptions(), null, sources);
+ SharedTypeAnnotationProcessor annotationProcessor = new SharedTypeAnnotationProcessor();
+ annotationProcessor.setUserProps(properties);
+ task.setProcessors(Collections.singleton(annotationProcessor));
+ task.call();
+ }
+ }
+
+ private List walkAllSourceFiles() throws MojoExecutionException {
+ SourceFileVisitor visitor = new SourceFileVisitor();
+ try {
+ for (String compileSourceRoot : project.getCompileSourceRoots()) {
+ Files.walkFileTree(Paths.get(compileSourceRoot), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor);
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException(e);
+ }
+ return visitor.getFiles();
+ }
+
+ private List getCompilerOptions() throws MojoFailureException {
+ List options = new ArrayList<>(DEFAULT_COMPILER_OPTIONS.size() + 1);
+ options.addAll(DEFAULT_COMPILER_OPTIONS);
+ if (propertyFile != null) {
+ if (Files.notExists(Paths.get(propertyFile))) {
+ throw new MojoFailureException("Property file not found: " + propertyFile);
+ }
+ options.add("-Asharedtype.propsFile=" + propertyFile);
+ }
+ return options;
+ }
+
+ private static JavaCompiler getJavaCompiler() throws MojoExecutionException {
+ String javaVersion = System.getProperty("java.specification.version");
+ JavaCompiler compiler;
+ if (JAVA8_VERSION.equals(javaVersion)) {
+ try {
+ Class> javacToolClass = SharedTypeAnnotationProcessor.class.getClassLoader().loadClass("com.sun.tools.javac.api.JavacTool");
+ compiler = (JavaCompiler) javacToolClass.getConstructor().newInstance();
+ } catch (Exception e) {
+ throw new MojoExecutionException("Failed to load JavaCompiler.", e);
+ }
+ } else {
+ compiler = ToolProvider.getSystemJavaCompiler();
+ }
+ if (compiler != null) {
+ return compiler;
+ }
+ throw new MojoExecutionException("Java compiler not found, currently only compiler from jdk.compiler module is supported.");
+ }
+
+ private Charset getCharset() throws MojoFailureException {
+ String encoding = project.getProperties().getProperty("project.build.sourceEncoding");
+ if (encoding != null) {
+ try {
+ return Charset.forName(encoding);
+ } catch (UnsupportedCharsetException e) {
+ throw new MojoFailureException("Invalid 'encoding' option: " + encoding, e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeLogger.java b/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeLogger.java
new file mode 100644
index 00000000..838cb9f7
--- /dev/null
+++ b/maven-plugin/src/main/java/online/sharedtype/maven/SharedTypeLogger.java
@@ -0,0 +1,44 @@
+package online.sharedtype.maven;
+
+import org.apache.maven.plugin.logging.Log;
+
+import java.io.Writer;
+
+final class SharedTypeLogger extends Writer {
+ private final Log log;
+ private final StringBuffer buffer = new StringBuffer();
+
+ SharedTypeLogger(Log log) {
+ this.log = log;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ buffer.append(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() {
+ removeNewLine(buffer);
+ String message = buffer.toString();
+ if (!message.isEmpty()) {
+ log.info(message);
+ }
+ buffer.setLength(0);
+ }
+
+ @Override
+ public void close() {
+ flush();
+ }
+
+ private static void removeNewLine(StringBuffer buffer) {
+ if (buffer.length() <= 0) {
+ return;
+ }
+ char c;
+ while ((c = buffer.charAt(buffer.length() - 1)) == '\n' || (c == '\r')) {
+ buffer.delete(buffer.length() - 1, buffer.length());
+ }
+ }
+}
diff --git a/maven-plugin/src/main/java/online/sharedtype/maven/SourceFileVisitor.java b/maven-plugin/src/main/java/online/sharedtype/maven/SourceFileVisitor.java
new file mode 100644
index 00000000..586e2ce6
--- /dev/null
+++ b/maven-plugin/src/main/java/online/sharedtype/maven/SourceFileVisitor.java
@@ -0,0 +1,29 @@
+package online.sharedtype.maven;
+
+import java.io.File;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Cause Chung
+ */
+final class SourceFileVisitor extends SimpleFileVisitor {
+ private static final String FILE_EXTENSION = ".java";
+ private final List files = new ArrayList<>();
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ if (file.toString().endsWith(FILE_EXTENSION)) {
+ files.add(file.toFile());
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ List getFiles() {
+ return files;
+ }
+}
diff --git a/maven-plugin/src/test/java/online/sharedtype/maven/SharedTypeDiagnosticListenerTest.java b/maven-plugin/src/test/java/online/sharedtype/maven/SharedTypeDiagnosticListenerTest.java
new file mode 100644
index 00000000..2d197761
--- /dev/null
+++ b/maven-plugin/src/test/java/online/sharedtype/maven/SharedTypeDiagnosticListenerTest.java
@@ -0,0 +1,53 @@
+package online.sharedtype.maven;
+
+import org.apache.maven.plugin.logging.Log;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+final class SharedTypeDiagnosticListenerTest {
+ private @Mock Diagnostic diagnostic;
+ private @Mock Log log;
+ private final Path baseDir = Paths.get("/test/project/dir");
+ private SharedTypeDiagnosticListener listener;
+
+ private @Mock JavaFileObject source;
+
+ @BeforeEach
+ void setup() {
+ listener = new SharedTypeDiagnosticListener(log, baseDir);
+ }
+
+ @Test
+ void skipIfNoSourceCode() {
+ when(diagnostic.getSource()).thenReturn(null);
+
+ var sb = new StringBuilder();
+ listener.addSourceInfo(sb, diagnostic);
+ assertThat(sb.toString()).isEmpty();
+ }
+
+ @Test
+ void printSourceInfo() {
+ when(diagnostic.getSource()).thenReturn(source);
+ when(source.getName()).thenReturn("/test/project/dir/src/test.java");
+ when(diagnostic.getLineNumber()).thenReturn(69L);
+ when(diagnostic.getColumnNumber()).thenReturn(102L);
+
+ var sb = new StringBuilder();
+ listener.addSourceInfo(sb, diagnostic);
+ assertThat(sb.toString()).isEqualTo("src/test.java:69:102 ");
+ }
+}
diff --git a/maven-plugin/src/test/java/online/sharedtype/maven/SharedTypeLoggerTest.java b/maven-plugin/src/test/java/online/sharedtype/maven/SharedTypeLoggerTest.java
new file mode 100644
index 00000000..d4fb1d30
--- /dev/null
+++ b/maven-plugin/src/test/java/online/sharedtype/maven/SharedTypeLoggerTest.java
@@ -0,0 +1,35 @@
+package online.sharedtype.maven;
+
+import org.apache.maven.plugin.logging.Log;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+final class SharedTypeLoggerTest {
+ private @Mock Log log;
+ private SharedTypeLogger logger;
+ private @Captor ArgumentCaptor messageCaptor;
+
+ @BeforeEach
+ void setup() {
+ logger = new SharedTypeLogger(log);
+ }
+
+ @Test
+ void removeEndNewLine() throws Exception {
+ logger.append("some ");
+ logger.append("content\r\n\n");
+
+ logger.flush();
+ verify(log).info(messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo("some content");
+ }
+}
diff --git a/mount-tmpfs.sh b/mount-tmpfs.sh
index fd66ae3e..b10e5bf8 100755
--- a/mount-tmpfs.sh
+++ b/mount-tmpfs.sh
@@ -22,6 +22,8 @@ mountTmpfs "$DIR/processor/target" 64M
mountTmpfs "$DIR/it/java17/target" 64M
mountTmpfs "$DIR/it/java8/target" 64M
mountTmpfs "$DIR/e2e/target" 64M
+mountTmpfs "$DIR/maven-plugin/target" 32M
+mountTmpfs "$DIR/maven-plugin/it/target" 16M
mountTmpfs "$MAVEN_REPO_DIR" 64M
mountTmpfs "$DIR/client-test/rust/target" 512M
mountTmpfs "$DIR/client-test/typescript/dist" 32M
diff --git a/pom.xml b/pom.xml
index 0be3d344..55607f90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
UTF-8
+ UTF-8
8
8
21
@@ -61,6 +62,11 @@
sharedtype
${project.version}
+
+ ${project.groupId}
+ sharedtype-ap
+ ${project.version}
+
org.checkerframework
checker-qual
@@ -264,6 +270,8 @@
annotation
processor
it
+ maven-plugin
+ maven-plugin/it
@@ -277,6 +285,7 @@
annotation
processor
+ maven-plugin
diff --git a/processor/pom.xml b/processor/pom.xml
index 1dad7caf..57974a1c 100644
--- a/processor/pom.xml
+++ b/processor/pom.xml
@@ -1,5 +1,6 @@
-
+
4.0.0
online.sharedtype
@@ -63,6 +64,50 @@
+
+
+
+ jdk-tools-jar-env-var
+
+ true
+
+ ${JAVA_HOME}/lib/tools.jar
+
+
+
+
+ ${JAVA_HOME}/lib/tools.jar
+
+
+
+ jdk-tools-jar-sys-prop
+
+ false
+
+ ${java.home}/../lib/tools.jar
+
+
+
+ ${java.home}/../lib/tools.jar
+
+
+
+ java8-jdk-tools-jar
+
+ 1.8
+
+
+
+ jdk.tools
+ jdk.tools
+ jdk1.8.0
+ system
+ ${toolsjar}
+
+
+
+
+
@@ -136,7 +181,9 @@
org.apache.maven.plugins
maven-surefire-plugin
- -javaagent:${org.mockito:mockito-core:jar}
+
+ -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar
+
diff --git a/processor/src/main/java/module-info.java b/processor/src/main/java/module-info.java
index fa3cb89a..78bd4a41 100644
--- a/processor/src/main/java/module-info.java
+++ b/processor/src/main/java/module-info.java
@@ -1,3 +1,5 @@
+import online.sharedtype.processor.SharedTypeAnnotationProcessor;
+
module online.sharedtype.processor {
requires online.sharedtype.annotation;
requires java.base;
@@ -8,5 +10,5 @@
requires com.github.mustachejava;
- provides javax.annotation.processing.Processor with online.sharedtype.processor.AnnotationProcessorImpl;
+ provides javax.annotation.processing.Processor with SharedTypeAnnotationProcessor;
}
diff --git a/processor/src/main/java/online/sharedtype/processor/AnnotationProcessorImpl.java b/processor/src/main/java/online/sharedtype/processor/SharedTypeAnnotationProcessor.java
similarity index 85%
rename from processor/src/main/java/online/sharedtype/processor/AnnotationProcessorImpl.java
rename to processor/src/main/java/online/sharedtype/processor/SharedTypeAnnotationProcessor.java
index ce3af180..44769f2d 100644
--- a/processor/src/main/java/online/sharedtype/processor/AnnotationProcessorImpl.java
+++ b/processor/src/main/java/online/sharedtype/processor/SharedTypeAnnotationProcessor.java
@@ -1,11 +1,13 @@
package online.sharedtype.processor;
import com.google.auto.service.AutoService;
+import lombok.Setter;
import online.sharedtype.processor.domain.def.TypeDef;
import online.sharedtype.processor.context.Context;
import online.sharedtype.processor.context.PropsFactory;
import online.sharedtype.processor.parser.TypeDefParser;
import online.sharedtype.processor.resolver.TypeResolver;
+import online.sharedtype.processor.support.annotation.Nullable;
import online.sharedtype.processor.support.annotation.VisibleForTesting;
import online.sharedtype.processor.support.exception.SharedTypeException;
import online.sharedtype.processor.support.exception.SharedTypeInternalError;
@@ -24,6 +26,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import static online.sharedtype.processor.domain.Constants.ANNOTATION_QUALIFIED_NAME;
@@ -36,10 +39,14 @@
@SupportedAnnotationTypes("online.sharedtype.SharedType")
@SupportedOptions({"sharedtype.propsFile", "sharedtype.enabled"})
@AutoService(Processor.class)
-public final class AnnotationProcessorImpl extends AbstractProcessor {
+public final class SharedTypeAnnotationProcessor extends AbstractProcessor {
private static final String PROPS_FILE_OPTION_NAME = "sharedtype.propsFile";
+ private static final String PROPS_ENABLED = "sharedtype.enabled";
private static final String DEFAULT_USER_PROPS_FILE = "sharedtype.properties";
private static final boolean ANNOTATION_CONSUMED = true;
+ /** Programmatically provided user properties, e.g. from Maven plugin */
+ @Nullable @Setter
+ private Map userProps;
private boolean enabled;
Context ctx;
TypeDefParser parser;
@@ -54,10 +61,10 @@ public SourceVersion getSupportedSourceVersion() {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
- String configFile = processingEnv.getOptions().getOrDefault(PROPS_FILE_OPTION_NAME, DEFAULT_USER_PROPS_FILE);
+ String userPropsFile = processingEnv.getOptions().getOrDefault(PROPS_FILE_OPTION_NAME, DEFAULT_USER_PROPS_FILE);
enabled = isEnabled(processingEnv);
if (enabled) {
- ctx = new Context(processingEnv, PropsFactory.loadProps(Paths.get(configFile)));
+ ctx = new Context(processingEnv, PropsFactory.loadProps(Paths.get(userPropsFile), userProps));
parser = TypeDefParser.create(ctx);
resolver = TypeResolver.create(ctx, parser);
writer = TypeWriter.create(ctx);
@@ -88,7 +95,8 @@ void doProcess(Set extends Element> elements) {
List typeDefs = parser.parse(typeElement);
discoveredDefs.addAll(typeDefs);
if (typeDefs.isEmpty()){
- ctx.warn(element, "Type '%s' is ignored or invalid, but annotated with '%s'.", typeElement.getQualifiedName().toString(), ANNOTATION_QUALIFIED_NAME);
+ ctx.warn(element, "Type '%s' is ignored or invalid, but annotated with '%s'.",
+ typeElement.getQualifiedName().toString(), ANNOTATION_QUALIFIED_NAME);
}
} else {
throw new SharedTypeInternalError(String.format("Unsupported element: %s of kind %s", element, element.getKind()));
@@ -103,7 +111,7 @@ void doProcess(Set extends Element> elements) {
}
private static boolean isEnabled(ProcessingEnvironment processingEnv) {
- String enabledExpr = processingEnv.getOptions().getOrDefault("sharedtype.enabled", "false");
+ String enabledExpr = processingEnv.getOptions().getOrDefault(PROPS_ENABLED, "false");
return enabledExpr.equalsIgnoreCase("true") || enabledExpr.equalsIgnoreCase("yes");
}
}
diff --git a/processor/src/main/java/online/sharedtype/processor/context/Props.java b/processor/src/main/java/online/sharedtype/processor/context/Props.java
index 4901c2ec..d7e526f5 100644
--- a/processor/src/main/java/online/sharedtype/processor/context/Props.java
+++ b/processor/src/main/java/online/sharedtype/processor/context/Props.java
@@ -5,7 +5,9 @@
import lombok.Getter;
import lombok.experimental.Accessors;
import online.sharedtype.SharedType;
+import online.sharedtype.processor.support.annotation.Nullable;
+import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
@@ -47,7 +49,8 @@ public static final class Typescript {
private final EnumFormat enumFormat;
private final FieldReadonlyType fieldReadonlyType;
private final Map typeMappings;
- private final String customCodePath;
+ @Nullable
+ private final Path customCodePath;
@Getter
public enum OptionalFieldFormat {
@@ -95,7 +98,8 @@ public static final class Go {
private final String targetDatetimeTypeLiteral;
private final EnumFormat enumFormat;
private final Map typeMappings;
- private final String customCodePath;
+ @Nullable
+ private final Path customCodePath;
public enum EnumFormat {
CONST, STRUCT,
@@ -118,6 +122,7 @@ public static final class Rust {
private final Set defaultTypeMacros;
private final String targetDatetimeTypeLiteral;
private final Map typeMappings;
- private final String customCodePath;
+ @Nullable
+ private final Path customCodePath;
}
}
diff --git a/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java b/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java
index 5c850a88..d0f9b749 100644
--- a/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java
+++ b/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java
@@ -1,11 +1,12 @@
package online.sharedtype.processor.context;
+import online.sharedtype.processor.support.annotation.Nullable;
import online.sharedtype.processor.support.exception.SharedTypeException;
-import online.sharedtype.processor.support.annotation.Nullable;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -24,7 +25,7 @@
public final class PropsFactory {
private static final String DEFAULT_PROPERTIES_FILE = "sharedtype-default.properties";
- public static Props loadProps(@Nullable Path userPropertiesFile) {
+ public static Props loadProps(@Nullable Path userPropertiesFile, @Nullable Map userProperties) {
ClassLoader classLoader = PropsFactory.class.getClassLoader();
try (InputStream defaultPropsInputstream = classLoader.getResourceAsStream(DEFAULT_PROPERTIES_FILE);
InputStream userPropsInputstream = userPropertiesFile == null || Files.notExists(userPropertiesFile) ? null : Files.newInputStream(userPropertiesFile)) {
@@ -33,8 +34,11 @@ public static Props loadProps(@Nullable Path userPropertiesFile) {
if (userPropsInputstream != null) {
properties.load(userPropsInputstream);
}
+ if (userProperties != null) {
+ properties.putAll(userProperties);
+ }
properties.putAll(System.getProperties());
- Props props = loadProps(properties);
+ Props props = convertProps(properties);
if (props.getTypescript().getOptionalFieldFormats().isEmpty()) {
throw new IllegalArgumentException("Props 'typescript.optional-field-format' cannot be empty.");
}
@@ -44,7 +48,7 @@ public static Props loadProps(@Nullable Path userPropertiesFile) {
}
}
- private static Props loadProps(Properties properties) {
+ private static Props convertProps(Properties properties) {
Set targets = parseEnumSet(properties, "sharedtype.targets", OutputTarget.class, OutputTarget::valueOf);
return Props.builder()
.targets(targets)
@@ -71,7 +75,7 @@ private static Props loadProps(Properties properties) {
.enumFormat(parseEnum(properties, "sharedtype.typescript.enum-format", Props.Typescript.EnumFormat::fromString))
.fieldReadonlyType(parseEnum(properties, "sharedtype.typescript.field-readonly-type", Props.Typescript.FieldReadonlyType::fromString))
.typeMappings(parseMap(properties, "sharedtype.typescript.type-mappings"))
- .customCodePath(properties.getProperty("sharedtype.typescript.custom-code-path"))
+ .customCodePath(resolvePath(properties, "sharedtype.typescript.custom-code-path"))
.build())
.go(Props.Go.builder()
.outputFileName(properties.getProperty("sharedtype.go.output-file-name"))
@@ -80,7 +84,7 @@ private static Props loadProps(Properties properties) {
.targetDatetimeTypeLiteral(properties.getProperty("sharedtype.go.target-datetime-type"))
.enumFormat(parseEnum(properties, "sharedtype.go.enum-format", Props.Go.EnumFormat::fromString))
.typeMappings(parseMap(properties, "sharedtype.go.type-mappings"))
- .customCodePath(properties.getProperty("sharedtype.go.custom-code-path"))
+ .customCodePath(resolvePath(properties,"sharedtype.go.custom-code-path"))
.build())
.rust(Props.Rust.builder()
.outputFileName(properties.getProperty("sharedtype.rust.output-file-name"))
@@ -89,7 +93,7 @@ private static Props loadProps(Properties properties) {
.defaultTypeMacros(splitArray(properties.getProperty("sharedtype.rust.default-macros-traits")))
.targetDatetimeTypeLiteral(properties.getProperty("sharedtype.rust.target-datetime-type"))
.typeMappings(parseMap(properties, "sharedtype.rust.type-mappings"))
- .customCodePath(properties.getProperty("sharedtype.rust.custom-code-path"))
+ .customCodePath(resolvePath(properties, "sharedtype.rust.custom-code-path"))
.hasEnumValueTypeAlias(parseBoolean(properties, "sharedtype.rust.enum-value-type-alias"))
.build())
.build();
@@ -167,6 +171,18 @@ private static Map parseMap(Properties properties, String proper
return map;
}
+ private static Path resolvePath(Properties properties, String propertyName) {
+ String value = properties.getProperty(propertyName);
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+ Path path = Paths.get(value);
+ if (Files.notExists(path)) {
+ throw new IllegalArgumentException(String.format("Invalid property %s=%s, file of path does not exist.", propertyName, path));
+ }
+ return path;
+ }
+
@SuppressWarnings("unchecked")
private static Class extends T> parseClass(String className) throws ClassNotFoundException {
return (Class extends T>) Class.forName(className);
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/JavaSerializationFileWriter.java b/processor/src/main/java/online/sharedtype/processor/writer/JavaSerializationFileWriter.java
index 7250e3b8..01dacba8 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/JavaSerializationFileWriter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/JavaSerializationFileWriter.java
@@ -1,5 +1,6 @@
package online.sharedtype.processor.writer;
+import lombok.RequiredArgsConstructor;
import online.sharedtype.processor.domain.def.ConstantNamespaceDef;
import online.sharedtype.processor.domain.def.TypeDef;
import online.sharedtype.processor.context.Context;
@@ -18,18 +19,15 @@
*
* @author Cause Chung
*/
+@RequiredArgsConstructor
final class JavaSerializationFileWriter implements TypeWriter {
- private final Filer filer;
-
- JavaSerializationFileWriter(Context ctx) {
- this.filer = ctx.getProcessingEnv().getFiler();
- }
+ private final Context ctx;
@Override
public void write(List typeDefs) {
try {
for (TypeDef typeDef : typeDefs) {
- FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", getTypeName(typeDef) + ".ser");
+ FileObject file = ctx.createSourceOutput(getTypeName(typeDef) + ".ser");
try(OutputStream outputStream = file.openOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {
oos.writeObject(typeDef);
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/TemplateTypeFileWriter.java b/processor/src/main/java/online/sharedtype/processor/writer/TemplateTypeFileWriter.java
index fd38c69a..9e764eec 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/TemplateTypeFileWriter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/TemplateTypeFileWriter.java
@@ -42,7 +42,7 @@ public void write(List typeDefs) throws IOException {
for (TypeDef typeDef : typeDefs) {
TypeDef duplicate = typeDef instanceof ConstantNamespaceDef ? null : simpleNames.get(typeDef.simpleName()); // todo: split class/enum and constant duplication checks
if (duplicate != null) {
- ctx.warn("Duplicate names found: %s and %s, which is not allowed in output code." +
+ ctx.warn("Duplicate names found: %s and %s, which may not be valid in output code." +
" You may use @SharedType(name=\"...\") to rename a type.", typeDef.qualifiedName(), duplicate.qualifiedName());
}
simpleNames.put(typeDef.simpleName(), typeDef);
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java
index ded1ec60..9766a807 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java
@@ -3,12 +3,12 @@
import lombok.RequiredArgsConstructor;
import online.sharedtype.processor.context.Context;
import online.sharedtype.processor.context.RenderFlags;
+import online.sharedtype.processor.support.annotation.Nullable;
import online.sharedtype.processor.support.exception.SharedTypeException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
@RequiredArgsConstructor
abstract class AbstractDataAdaptor implements RenderDataAdaptor {
@@ -21,12 +21,15 @@ final RenderFlags renderFlags() {
abstract String customCodeSnippet();
- static String readCustomCodeSnippet(String path) {
- Path customCodePath = Paths.get(path);
- if (Files.notExists(customCodePath)) {
+ static String readCustomCodeSnippet(@Nullable Path customCodePath) {
+ if (customCodePath == null) {
return "";
}
+ if (Files.notExists(customCodePath)) { // this should be checked before calling this method when properties are loaded
+ throw new SharedTypeException(String.format("Custom code snippet not found at path '%s'", customCodePath));
+ }
+
try {
return new String(Files.readAllBytes(customCodePath));
} catch (IOException e) {
diff --git a/processor/src/main/resources/sharedtype-default.properties b/processor/src/main/resources/sharedtype-default.properties
index 7c56333d..6dd1f756 100644
--- a/processor/src/main/resources/sharedtype-default.properties
+++ b/processor/src/main/resources/sharedtype-default.properties
@@ -83,9 +83,8 @@ sharedtype.typescript.field-readonly-type=acyclic
sharedtype.typescript.type-mappings=
## Path to custom code snippet file, code snippet in the file will be injected into the top of generated file
-## If the file does not exist, it will be ignored
-## Below default assumes the file is on the cmd execution path
-sharedtype.typescript.custom-code-path=sharedtype-custom-code.ts
+## If the file of the cannot be found, an error will be thrown
+sharedtype.typescript.custom-code-path=
##############################
# Golang specific properties #
@@ -112,9 +111,8 @@ sharedtype.go.enum-format=const
sharedtype.go.type-mappings=
## Path to custom code snippet file, code snippet in the file will be injected into the top of generated file
-## If the file does not exist, it will be ignored
-## Below default assumes the file is on the cmd execution path
-sharedtype.go.custom-code-path=sharedtype-custom-code.go
+## If the file of the cannot be found, an error will be thrown
+sharedtype.go.custom-code-path=
############################
# Rust specific properties #
@@ -146,6 +144,5 @@ sharedtype.rust.target-datetime-type=String
sharedtype.rust.type-mappings=
## Path to custom code snippet file, code snippet in the file will be injected into the top of generated file
-## If the file does not exist, it will be ignored
-## Below default assumes the file is on the cmd execution path
-sharedtype.rust.custom-code-path=sharedtype-custom-code.rs
+## If the file of the cannot be found, an error will be thrown
+sharedtype.rust.custom-code-path=
diff --git a/processor/src/test/java/online/sharedtype/processor/AnnotationProcessorImplTest.java b/processor/src/test/java/online/sharedtype/processor/SharedTypeAnnotationProcessorTest.java
similarity index 94%
rename from processor/src/test/java/online/sharedtype/processor/AnnotationProcessorImplTest.java
rename to processor/src/test/java/online/sharedtype/processor/SharedTypeAnnotationProcessorTest.java
index dc04c6ac..a8ed7275 100644
--- a/processor/src/test/java/online/sharedtype/processor/AnnotationProcessorImplTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/SharedTypeAnnotationProcessorTest.java
@@ -20,12 +20,12 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-class AnnotationProcessorImplTest {
+class SharedTypeAnnotationProcessorTest {
private final ContextMocks ctxMocks = new ContextMocks();
private final TypeDefParser typeDefParser = mock(TypeDefParser.class);
private final TypeResolver typeResolver = mock(TypeResolver.class);
private final TypeWriter typeWriter = mock(TypeWriter.class);
- private final AnnotationProcessorImpl processor = new AnnotationProcessorImpl();
+ private final SharedTypeAnnotationProcessor processor = new SharedTypeAnnotationProcessor();
private final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class);
diff --git a/processor/src/test/java/online/sharedtype/processor/context/ContextMocks.java b/processor/src/test/java/online/sharedtype/processor/context/ContextMocks.java
index 30cdaf00..958d8caa 100644
--- a/processor/src/test/java/online/sharedtype/processor/context/ContextMocks.java
+++ b/processor/src/test/java/online/sharedtype/processor/context/ContextMocks.java
@@ -29,7 +29,7 @@ public final class ContextMocks {
private final Context context = mock(Context.class);
public ContextMocks() {
- this.props = spy(PropsFactory.loadProps(null));
+ this.props = spy(PropsFactory.loadProps(null, null));
when(context.getProps()).thenReturn(props);
when(context.getProcessingEnv()).thenReturn(processingEnv);
when(processingEnv.getElementUtils()).thenReturn(elements);
diff --git a/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java b/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java
index 6e35982f..9bbb2520 100644
--- a/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java
@@ -3,10 +3,12 @@
import online.sharedtype.SharedType;
import org.junit.jupiter.api.Test;
import online.sharedtype.processor.support.exception.SharedTypeException;
+import org.junitpioneer.jupiter.SetSystemProperty;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -14,16 +16,33 @@
final class PropsFactoryTest {
@Test
- void loadUserProps() {
- Props props = PropsFactory.loadProps(resolveResource("test-sharedtype-user.properties"));
+ void loadUserPropsFile() {
+ Props props = PropsFactory.loadProps(resolveResource("test-sharedtype-user.properties"), null);
assertThat(props.getTargets()).containsExactly(OutputTarget.CONSOLE, OutputTarget.TYPESCRIPT);
assertThat(props.getOptionalAnnotations()).containsExactly("a.b.TsOptional");
assertThat(props.getTypescript().getJavaObjectMapType()).isEqualTo("unknown");
}
+ @Test
+ void loadUserProps() {
+ var userProps = Map.of("sharedtype.optional-annotations", "a.b.TsOptionalOverride");
+ Props props = PropsFactory.loadProps(resolveResource("test-sharedtype-user.properties"), userProps);
+ assertThat(props.getTargets()).containsExactly(OutputTarget.CONSOLE, OutputTarget.TYPESCRIPT);
+ assertThat(props.getOptionalAnnotations()).containsExactly("a.b.TsOptionalOverride");
+ }
+
+ @SetSystemProperty(key = "sharedtype.optional-annotations", value = "a.b.TsOptionalOverride2")
+ @Test
+ void loadSysProps() {
+ var userProps = Map.of("sharedtype.optional-annotations", "a.b.TsOptionalOverride");
+ Props props = PropsFactory.loadProps(resolveResource("test-sharedtype-user.properties"), userProps);
+ assertThat(props.getTargets()).containsExactly(OutputTarget.CONSOLE, OutputTarget.TYPESCRIPT);
+ assertThat(props.getOptionalAnnotations()).containsExactly("a.b.TsOptionalOverride2");
+ }
+
@Test
void loadDefaultProps() {
- Props props = PropsFactory.loadProps(Paths.get("not-exist"));
+ Props props = PropsFactory.loadProps(Paths.get("not-exist"), null);
assertThat(props.getTargets()).containsExactly(OutputTarget.TYPESCRIPT);
assertThat(props.getTargetTypes()).containsExactly(SharedType.TargetType.TYPESCRIPT);
assertThat(props.getOptionalAnnotations()).containsExactly("javax.annotation.Nullable");
@@ -56,7 +75,7 @@ void loadDefaultProps() {
assertThat(typescriptProps.getEnumFormat()).isEqualTo(Props.Typescript.EnumFormat.UNION);
assertThat(typescriptProps.getFieldReadonlyType()).isEqualTo(Props.Typescript.FieldReadonlyType.ACYCLIC);
assertThat(typescriptProps.getTypeMappings()).isEmpty();
- assertThat(typescriptProps.getCustomCodePath()).isEqualTo("sharedtype-custom-code.ts");
+ assertThat(typescriptProps.getCustomCodePath()).isNull();
Props.Go goProps = props.getGo();
assertThat(goProps.getOutputFileName()).isEqualTo("types.go");
@@ -65,7 +84,7 @@ void loadDefaultProps() {
assertThat(goProps.getTargetDatetimeTypeLiteral()).isEqualTo("string");
assertThat(goProps.getEnumFormat()).isEqualTo(Props.Go.EnumFormat.CONST);
assertThat(goProps.getTypeMappings()).isEmpty();
- assertThat(goProps.getCustomCodePath()).isEqualTo("sharedtype-custom-code.go");
+ assertThat(goProps.getCustomCodePath()).isNull();
Props.Rust rustProps = props.getRust();
assertThat(rustProps.getOutputFileName()).isEqualTo("types.rs");
@@ -75,18 +94,25 @@ void loadDefaultProps() {
assertThat(rustProps.getDefaultTypeMacros()).containsExactly("Debug");
assertThat(rustProps.getTargetDatetimeTypeLiteral()).isEqualTo("String");
assertThat(rustProps.getTypeMappings()).isEmpty();
- assertThat(rustProps.getCustomCodePath()).isEqualTo("sharedtype-custom-code.rs");
+ assertThat(rustProps.getCustomCodePath()).isNull();
+ }
+
+ @Test
+ void failWhenCustomCodeFilePathProvidedButNotExists() {
+ var userProps = Map.of("sharedtype.typescript.custom-code-path", "not-exists.ts");
+ assertThatThrownBy(() -> PropsFactory.loadProps(null, userProps)).isInstanceOf(SharedTypeException.class);
}
@Test
void wrongTarget() {
- assertThatThrownBy(() -> PropsFactory.loadProps(resolveResource("test-sharedtype-wrong-target.properties")))
- .isInstanceOf(SharedTypeException.class);
+ var userProps = Map.of("sharedtype.targets", "ENGLISH");
+ assertThatThrownBy(() -> PropsFactory.loadProps(null, userProps)).isInstanceOf(SharedTypeException.class);
}
@Test
void wrongTypescriptOptionalFieldFormat() {
- assertThatThrownBy(() -> PropsFactory.loadProps(resolveResource("test-sharedtype-wrong-ts-optional-field-format.properties")))
+ var userProps = Map.of("sharedtype.typescript.optional-field-format", "abc,?");
+ assertThatThrownBy(() -> PropsFactory.loadProps(null, userProps))
.isInstanceOf(SharedTypeException.class)
.cause().cause()
.hasMessageContaining("Unknown optional field format: 'abc', only '?', 'null', 'undefined' are allowed");
@@ -94,7 +120,7 @@ void wrongTypescriptOptionalFieldFormat() {
@Test
void typeMappings() {
- Props props = PropsFactory.loadProps(resolveResource("test-sharedtype-type-mappings.properties"));
+ Props props = PropsFactory.loadProps(resolveResource("test-sharedtype-type-mappings.properties"), null);
assertThat(props.getTypescript().getTypeMappings()).containsExactly(
entry("MyType1", "RenamedType1"),
entry("MyType2", "RenamedType2")
@@ -103,7 +129,9 @@ void typeMappings() {
private static Path resolveResource(String resource) {
try {
- return Paths.get(PropsFactoryTest.class.getClassLoader().getResource(resource).toURI());
+ var testRes = PropsFactoryTest.class.getClassLoader().getResource(resource);
+ assert testRes != null;
+ return Paths.get(testRes.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
diff --git a/processor/src/test/java/online/sharedtype/processor/writer/adaptor/TypescriptHeaderDataAdaptorTest.java b/processor/src/test/java/online/sharedtype/processor/writer/adaptor/TypescriptHeaderDataAdaptorTest.java
index ee374a11..6274f3a2 100644
--- a/processor/src/test/java/online/sharedtype/processor/writer/adaptor/TypescriptHeaderDataAdaptorTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/writer/adaptor/TypescriptHeaderDataAdaptorTest.java
@@ -4,7 +4,10 @@
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.SetSystemProperty;
+import java.nio.file.Paths;
+
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
final class TypescriptHeaderDataAdaptorTest {
@@ -16,11 +19,9 @@ void readCustomCodeSnippet() {
assertThat(adaptor.customCodeSnippet()).isEqualTo("interface A {}" + System.lineSeparator());
}
- @SetSystemProperty(key = "sharedtype.typescript.custom-code-path", value = "not-exists.ts")
@Test
void customCodeSnippetNoFile() {
- ContextMocks ctxMocks = new ContextMocks();
- TypescriptHeaderDataAdaptor adaptor = new TypescriptHeaderDataAdaptor(ctxMocks.getContext());
- assertThat(adaptor.customCodeSnippet()).isEqualTo("");
+ assertThatThrownBy(() -> TypescriptHeaderDataAdaptor.readCustomCodeSnippet(Paths.get("not-exists.ts")));
+ assertThat(TypescriptHeaderDataAdaptor.readCustomCodeSnippet(null)).isEqualTo("");
}
}
diff --git a/processor/src/test/resources/test-sharedtype-wrong-target.properties b/processor/src/test/resources/test-sharedtype-wrong-target.properties
deleted file mode 100644
index 6ca45e9b..00000000
--- a/processor/src/test/resources/test-sharedtype-wrong-target.properties
+++ /dev/null
@@ -1 +0,0 @@
-sharedtype.targets=ENGLISH,
diff --git a/processor/src/test/resources/test-sharedtype-wrong-ts-optional-field-format.properties b/processor/src/test/resources/test-sharedtype-wrong-ts-optional-field-format.properties
deleted file mode 100644
index 0451512d..00000000
--- a/processor/src/test/resources/test-sharedtype-wrong-ts-optional-field-format.properties
+++ /dev/null
@@ -1 +0,0 @@
-sharedtype.typescript.optional-field-format=abc,?
diff --git a/setenv b/setenv
index 2d61d8bf..de1330f5 100755
--- a/setenv
+++ b/setenv
@@ -23,7 +23,7 @@ export MAVEN_OPTS="-Xmx512m -Xms512m"
export PATH=$JAVA_HOME/bin:$MVND_HOME/bin:$PATH
if [ -z "$MVND_HOME" ];then
- echo "MVND_HOME is not set, mvnd is recommended for local development, see https://github.com/apache/maven-mvnd"
+ echo "MVND_HOME is not set, mvnd can speed up local build, see https://github.com/apache/maven-mvnd. (mvnd currently does not update plugins on each run, when developing maven plugin, use mvnw)"
java -version
else
mvnd -version