diff --git a/build.gradle.kts b/build.gradle.kts index da4590e..95b32a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,7 +27,13 @@ repositories { mavenCentral() } +val detag by configurations.creating { + isCanBeConsumed = false + isCanBeResolved = true +} + dependencies { + detag("com.r35157.tools:detag-impl_ref:0.1-dev") compileOnly("org.jetbrains:annotations:26.1.0") } @@ -39,6 +45,75 @@ tasks.withType().configureEach { options.release.set(25) } +val generatedDetagMain = layout.buildDirectory.dir("generated/sources/detag/main/java") + +val cleanGeneratedDetagMain by tasks.registering(Delete::class) { + delete(generatedDetagMain) +} + +val detagMain by tasks.registering(JavaExec::class) { + group = "build" + description = "Generates Java sources from .tjava files" + + classpath = detag + mainClass.set("com.r35157.tools.detag.impl.ref.Main") + + val configFile = layout.projectDirectory.file("../detag.conf") + val sourceRoot = layout.projectDirectory.dir("src/main/tjava") + + inputs.file(configFile) + inputs.dir(sourceRoot) + outputs.dir(generatedDetagMain) + + dependsOn(cleanGeneratedDetagMain) + + args( + "--config", configFile.asFile.absolutePath, + "--source-root", sourceRoot.asFile.absolutePath, + "--out", generatedDetagMain.get().asFile.absolutePath + ) +} + +sourceSets { + main { + // Human-written Detag source files. IntelliJ should treat this as a source root. + // Gradle's Java compiler will still only compile .java files directly from sourceSets, + // so the .tjava files are not compiled directly. + java.srcDir("src/main/tjava") + } +} + +tasks.named("compileJava") { + dependsOn(detagMain) + + // Compiler input generated from src/main/tjava. + // Do not add this directory to sourceSets, or IntelliJ will see duplicate classes: + // MyClass.tjava + build/generated/.../MyClass.java. + source(generatedDetagMain) +} + +val libsDir = layout.buildDirectory.dir("libs") + +tasks.register("prepareLibs") { + group = "distribution" + description = "Copies runtime deps to build/libs without deleting the app jar" + + val jarTask = tasks.named("jar") + dependsOn(jarTask) + + into(libsDir) + + // Kun deps (transitivt) + from(configurations.runtimeClasspath) + + // Bevar jar-filen som jar-tasken allerede har lagt i build/libs + preserve { + include(jarTask.get().archiveFileName.get()) + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + publishing { publications { create("mavenJava") { diff --git a/build.sh b/build.sh index 1327595..d0fd1b5 100755 --- a/build.sh +++ b/build.sh @@ -5,5 +5,11 @@ export VERSION=$(git describe --tags --exact-match 2>/dev/null \ || git symbolic-ref --short -q HEAD \ || git rev-parse --short HEAD) -echo "Building $VERSION..." +GITHASH=$(git rev-parse --short=8 HEAD) + +export VERSION_LONG=${VERSION}_${GITHASH} + +# Build this artifact +echo "Building 'NenjimHub API v${VERSION_LONG}'..." + ./gradlew -Pversion=$VERSION jar diff --git a/src/main/java/com/r35157/libs/valuetypes/basic/CurrencyType.java b/src/main/java/com/r35157/libs/valuetypes/basic/CurrencyType.java new file mode 100644 index 0000000..dd51822 --- /dev/null +++ b/src/main/java/com/r35157/libs/valuetypes/basic/CurrencyType.java @@ -0,0 +1,16 @@ +package com.r35157.libs.valuetypes.basic; + +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public record CurrencyType( + UUID id, + String name, + String symbol +) { + @Override + public @NotNull String toString() { + return symbol; + } +} diff --git a/src/main/java/com/r35157/libs/valuetypes/basic/SemanticVersion.java b/src/main/java/com/r35157/libs/valuetypes/basic/SemanticVersion.java new file mode 100644 index 0000000..d8cc612 --- /dev/null +++ b/src/main/java/com/r35157/libs/valuetypes/basic/SemanticVersion.java @@ -0,0 +1,70 @@ +package com.r35157.libs.valuetypes.basic; + +import org.jetbrains.annotations.NotNull; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Semantic Versioning (SemVer): + * A version number has the form MAJOR.MINOR.PATCH. + * - Increment MAJOR for incompatible API changes, + * - Increment MINOR for added functionality in a backward-compatible way, + * - Increment PATCH for backward-compatible bug fixes or improvements. + * TODO: Not the whole specification is implemented yet! + */ +public record SemanticVersion(int major, int minor, int patch) { + /** + * Creates a SemanticVersion and validates that all components are valid. + * + * @throws IllegalArgumentException if any of {@code major}, {@code minor}, or {@code patch} is negative + */ + public SemanticVersion { + initializationGuardClause(major, minor, patch); + } + + public static SemanticVersion of(int major, int minor, int patch) { + return new SemanticVersion(major, minor, patch); + } + + public static SemanticVersion of(int major, int minor) { + return of(major, minor, 0); + } + + public static SemanticVersion of(int major) { + return of(major, 0); + } + + public static SemanticVersion of(@NotNull String versionStr) { + final String s = versionStr.trim(); + + final Matcher m = SEMVER_REGEX.matcher(s); + if (!m.matches()) { + throw new IllegalArgumentException("Invalid semantic version: '" + versionStr + "'!"); + } + + try { + final int major = Integer.parseInt(m.group(1)); + final int minor = m.group(2) != null ? Integer.parseInt(m.group(2)) : 0; + final int patch = m.group(3) != null ? Integer.parseInt(m.group(3)) : 0; + return of(major, minor, patch); + } catch (NumberFormatException e) { + // Happens only with overruns + throw new IllegalArgumentException("Invalid semantic version: '" + versionStr + "'!", e); + } + } + + @Override + public @NotNull String toString() { + return "%d.%d.%d".formatted(major, minor, patch); + } + + private void initializationGuardClause(int major, int minor, int patch) throws IllegalArgumentException { + if (major < 0) throw new IllegalArgumentException("Version element 'major' cannot be negative - was '" + major + "'!"); + if (minor < 0) throw new IllegalArgumentException("Version element 'minor' cannot be negative - was '" + minor + "'!"); + if (patch < 0) throw new IllegalArgumentException("Version element 'patch' cannot be negative - was '" + patch + "'!"); + } + + private static final Pattern SEMVER_REGEX = + Pattern.compile("^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?$"); +} diff --git a/src/main/java/com/r35157/libs/valuetypes/basic/SmtpConfiguration.java b/src/main/java/com/r35157/libs/valuetypes/basic/SmtpConfiguration.java new file mode 100644 index 0000000..b577cd1 --- /dev/null +++ b/src/main/java/com/r35157/libs/valuetypes/basic/SmtpConfiguration.java @@ -0,0 +1,7 @@ +package com.r35157.libs.valuetypes.basic; + +public record SmtpConfiguration( + NetworkEndPoint networkEndPoint, + Credentials credentials +) { +} \ No newline at end of file diff --git a/src/main/java/com/r35157/libs/valuetypes/basic/TradingPair.java b/src/main/java/com/r35157/libs/valuetypes/basic/TradingPair.java new file mode 100644 index 0000000..c028ff6 --- /dev/null +++ b/src/main/java/com/r35157/libs/valuetypes/basic/TradingPair.java @@ -0,0 +1,6 @@ +package com.r35157.libs.valuetypes.basic; + +public record TradingPair( + CurrencyType base, // The thing you are buying or selling. + CurrencyType quote // The currency/unit used to price the base asset. +) { } diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/Credentials.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/Credentials.tjava new file mode 100644 index 0000000..cee99f0 --- /dev/null +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/Credentials.tjava @@ -0,0 +1,6 @@ +package com.r35157.libs.valuetypes.basic; + +public record Credentials( + ΩUserNameΩ userName, + ΩPasswordΩ password +) {} diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/MoneyAmount.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/MoneyAmount.tjava new file mode 100644 index 0000000..4685432 --- /dev/null +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/MoneyAmount.tjava @@ -0,0 +1,15 @@ +package com.r35157.libs.valuetypes.basic; + +import org.jetbrains.annotations.NotNull; + +import java.math.BigDecimal; + +public record MoneyAmount( + ΩAmountΩ amount, + CurrencyType currencyType +) { + @Override + public @NotNull String toString() { + return amount + " " + currencyType(); + } +} diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/MoneyPrice.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/MoneyPrice.tjava new file mode 100644 index 0000000..4480d0d --- /dev/null +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/MoneyPrice.tjava @@ -0,0 +1,8 @@ +package com.r35157.libs.valuetypes.basic; + +import java.math.BigDecimal; + +public record MoneyPrice( + ΩPriceΩ price, + CurrencyType currencyType +) { } diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/NetworkEndPoint.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/NetworkEndPoint.tjava new file mode 100644 index 0000000..28632c8 --- /dev/null +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/NetworkEndPoint.tjava @@ -0,0 +1,7 @@ +package com.r35157.libs.valuetypes.basic; + +public record NetworkEndPoint( + ΩHostnameΩ hostName, + ΩportNumberΩ portNumber +) { +} \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/Range.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/Range.tjava new file mode 100644 index 0000000..e1b7ed9 --- /dev/null +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/Range.tjava @@ -0,0 +1,43 @@ +package com.r35157.libs.valuetypes.basic; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public record Range>( + T from, + boolean fromIncluding, + T to, + boolean toIncluding +) { + public Range { + Objects.requireNonNull(from, "from"); + Objects.requireNonNull(to, "to"); + + if (from.compareTo(to) > 0) { + throw new IllegalArgumentException("Range from-value must not be greater than to-value."); + } + } + + public boolean contains(T value) { + Objects.requireNonNull(value, "value"); + + int fromComparison = value.compareTo(from); + int toComparison = value.compareTo(to); + + boolean afterFrom = fromIncluding + ? fromComparison >= 0 + : fromComparison > 0; + + boolean beforeTo = toIncluding + ? toComparison <= 0 + : toComparison < 0; + + return afterFrom && beforeTo; + } + + @Override + public @NotNull String toString() { + return (fromIncluding() ? "[" : "(") + from + "," + to + (toIncluding() ? "]" : ")"); + } +} \ No newline at end of file