Merge pull request #87 from onixbyte/develop

v3.4.0 for common-toolbox, crypto-toolbox and version-catalogue
This commit is contained in:
2026-05-29 16:30:17 +08:00
committed by GitHub
13 changed files with 252 additions and 170 deletions
+120 -19
View File
@@ -1,28 +1,100 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
# This workflow publishes one or more modules to Maven Central when a version tag is pushed
# to the main branch.
#
# Supported tag formats:
# <module>/v<version> — publish a single module (e.g. tuple/v3.3.1)
# <module>+<module>/v<version> — publish multiple modules (e.g. tuple+crypto-toolbox/v3.3.1)
# v<version> — publish all modules (e.g. v3.4.0)
#
# Valid module names: common-toolbox, tuple, identity-generator, crypto-toolbox, math-toolbox, version-catalogue
name: Publish Packages to GitHub Packages with Gradle
name: Publish Packages to Maven Central
on:
release:
types:
- published
push:
tags:
- 'v[0-9]*.[0-9]*.[0-9]*'
- '*/v[0-9]*.[0-9]*.[0-9]*'
jobs:
build:
publish:
name: Build and Publish
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
- name: Verify Tag is on Main Branch
run: |
if ! git merge-base --is-ancestor HEAD origin/main; then
echo "::error::Tag ${{ github.ref_name }} does not point to a commit on the main branch"
echo "Tags must be pushed after the commit is merged to main."
exit 1
fi
echo "✓ Tag ${{ github.ref_name }} is on main"
- name: Parse Tag
id: parse-tag
run: |
declare -A MODULE_PROPS=(
["common-toolbox"]="commonToolboxVersion"
["tuple"]="tupleVersion"
["identity-generator"]="identityGeneratorVersion"
["crypto-toolbox"]="cryptoToolboxVersion"
["math-toolbox"]="mathToolboxVersion"
["version-catalogue"]="versionCatalogueVersion"
)
TAG="${{ github.ref_name }}"
echo "Tag: ${TAG}"
# <module>[+<module>...]/v<version> — one or more specific modules
if [[ "${TAG}" =~ ^([a-z][a-z0-9-]+(\+[a-z][a-z0-9-]+)*)/v?([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then
IFS='+' read -ra MODULES <<< "${BASH_REMATCH[1]}"
VERSION="${BASH_REMATCH[3]}"
# v<version> — all modules
else
MODULES=("common-toolbox" "tuple" "identity-generator" "crypto-toolbox" "math-toolbox" "version-catalogue")
VERSION="${TAG#v}"
fi
# Validate all modules
for m in "${MODULES[@]}"; do
if [ -z "${MODULE_PROPS[$m]}" ]; then
echo "::error::Unknown module: ${m}"
echo "Valid modules: ${!MODULE_PROPS[*]}"
exit 1
fi
done
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "count=${#MODULES[@]}" >> $GITHUB_OUTPUT
for m in "${MODULES[@]}"; do
echo "→ ${m} @ ${VERSION}"
done
# Store module list as a multi-line output
{
echo "modules<<MODULES_EOF"
printf '%s\n' "${MODULES[@]}"
echo "MODULES_EOF"
} >> $GITHUB_OUTPUT
# Store property mappings
{
echo "props<<PROPS_EOF"
for m in "${MODULES[@]}"; do
echo "${m}=${MODULE_PROPS[$m]}"
done
echo "PROPS_EOF"
} >> $GITHUB_OUTPUT
- name: Setup GPG TTY
run: export GPG_TTY=$(tty)
@@ -61,19 +133,48 @@ jobs:
run: chmod +x ./gradlew
- name: Build with Gradle
# Overwrite artefactVersion with tag name
run: ./gradlew build -PartefactVersion=${{ github.event.release.tag_name }}
env:
MODULES: ${{ steps.parse-tag.outputs.modules }}
PROPS: ${{ steps.parse-tag.outputs.props }}
VERSION: ${{ steps.parse-tag.outputs.version }}
run: |
declare -A MODULE_PROPS
while IFS='=' read -r key value; do
MODULE_PROPS[$key]="$value"
done <<< "$PROPS"
while IFS= read -r MODULE; do
echo "::group::Building ${MODULE}"
PROP="${MODULE_PROPS[$MODULE]}"
./gradlew ":${MODULE}:build" "-P${PROP}=${VERSION}"
echo "::endgroup::"
done <<< "$MODULES"
- name: List Output Items
run: ls -l ./**/build/libs
- name: Publish to Maven Central
run: ./gradlew publish
env:
MODULES: ${{ steps.parse-tag.outputs.modules }}
PROPS: ${{ steps.parse-tag.outputs.props }}
VERSION: ${{ steps.parse-tag.outputs.version }}
run: |
declare -A MODULE_PROPS
while IFS='=' read -r key value; do
MODULE_PROPS[$key]="$value"
done <<< "$PROPS"
while IFS= read -r MODULE; do
echo "::group::Publishing ${MODULE}"
PROP="${MODULE_PROPS[$MODULE]}"
./gradlew ":${MODULE}:publish" "-P${PROP}=${VERSION}"
echo "::endgroup::"
done <<< "$MODULES"
- name: Create Deployment on Central Publisher Portal
run: |
curl --fail -X 'POST' \
'https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.onixbyte?publishing_type=user_managed' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.MAVEN_PORTAL_TOKEN }}' \
-d ''
'https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.onixbyte?publishing_type=user_managed' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.MAVEN_PORTAL_TOKEN }}' \
-d ''
-3
View File
@@ -20,9 +20,6 @@
* SOFTWARE.
*/
val artefactVersion: String by project
subprojects {
group = "com.onixbyte"
version = artefactVersion
}
+4 -2
View File
@@ -29,7 +29,9 @@ plugins {
id("signing")
}
val artefactVersion: String by project
val commonToolboxVersion: String by project
version = commonToolboxVersion
val projectUrl: String by project
val projectGithubUrl: String by project
val licenseName: String by project
@@ -70,7 +72,7 @@ publishing {
create<MavenPublication>("commonToolbox") {
groupId = group.toString()
artifactId = "common-toolbox"
version = artefactVersion
version = commonToolboxVersion
pom {
name = "OnixByte Common Toolbox"
@@ -1,128 +0,0 @@
/*
* Copyright (C) 2024-2025 OnixByte.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.
*/
package com.onixbyte.common.util;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HashUtilTest {
// Test MD2 hashing with explicit charset and default charset
@Test
void testMd2() {
String input = "test";
// Known MD2 hash of "test" with UTF-8
String expectedHash = "dd34716876364a02d0195e2fb9ae2d1b";
assertEquals(expectedHash, HashUtil.md2(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.md2(input));
// Test null charset fallback to UTF-8
assertEquals(expectedHash, HashUtil.md2(input, null));
}
// Test MD5 hashing with explicit charset and default charset
@Test
void testMd5() {
String input = "test";
// Known MD5 hash of "test"
String expectedHash = "098f6bcd4621d373cade4e832627b4f6";
assertEquals(expectedHash, HashUtil.md5(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.md5(input));
assertEquals(expectedHash, HashUtil.md5(input, null));
}
// Test SHA-1 hashing with explicit charset and default charset
@Test
void testSha1() {
String input = "test";
// Known SHA-1 hash of "test"
String expectedHash = "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3";
assertEquals(expectedHash, HashUtil.sha1(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.sha1(input));
assertEquals(expectedHash, HashUtil.sha1(input, null));
}
// Test SHA-224 hashing with explicit charset and default charset
@Test
void testSha224() {
String input = "test";
// Known SHA-224 hash of "test"
String expectedHash = "90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809";
assertEquals(expectedHash, HashUtil.sha224(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.sha224(input));
assertEquals(expectedHash, HashUtil.sha224(input, null));
}
// Test SHA-256 hashing with explicit charset and default charset
@Test
void testSha256() {
String input = "test";
// Known SHA-256 hash of "test"
String expectedHash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08";
assertEquals(expectedHash, HashUtil.sha256(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.sha256(input));
assertEquals(expectedHash, HashUtil.sha256(input, null));
}
// Test SHA-384 hashing with explicit charset and default charset
@Test
void testSha384() {
String input = "test";
// Known SHA-384 hash of "test"
String expectedHash = "768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9";
assertEquals(expectedHash, HashUtil.sha384(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.sha384(input));
assertEquals(expectedHash, HashUtil.sha384(input, null));
}
// Test SHA-512 hashing with explicit charset and default charset
@Test
void testSha512() {
String input = "test";
// Known SHA-512 hash of "test"
String expectedHash = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff";
// remove all whitespace in expected to match format generated
expectedHash = expectedHash.replaceAll("\\s+", "");
assertEquals(expectedHash, HashUtil.sha512(input, StandardCharsets.UTF_8));
assertEquals(expectedHash, HashUtil.sha512(input));
assertEquals(expectedHash, HashUtil.sha512(input, null));
}
// Test empty string input
@Test
void testEmptyString() {
String input = "";
// MD5 hash of empty string
String expectedMd5 = "d41d8cd98f00b204e9800998ecf8427e";
assertEquals(expectedMd5, HashUtil.md5(input));
// SHA-256 hash of empty string
String expectedSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
assertEquals(expectedSha256, HashUtil.sha256(input));
}
// Test null charset fallback for one algorithm as a sample
@Test
void testNullCharsetFallsBackToUtf8() {
String input = "abc";
String hashWithNull = HashUtil.md5(input, null);
String hashWithUtf8 = HashUtil.md5(input, StandardCharsets.UTF_8);
assertEquals(hashWithUtf8, hashWithNull);
}
}
+4 -2
View File
@@ -29,7 +29,9 @@ plugins {
id("signing")
}
val artefactVersion: String by project
val cryptoToolboxVersion: String by project
version = cryptoToolboxVersion
val projectUrl: String by project
val projectGithubUrl: String by project
val licenseName: String by project
@@ -73,7 +75,7 @@ publishing {
create<MavenPublication>("cryptoToolbox") {
groupId = group.toString()
artifactId = "crypto-toolbox"
version = artefactVersion
version = cryptoToolboxVersion
pom {
name = "OnixByte Crypto Toolbox"
@@ -22,6 +22,12 @@
package com.onixbyte.crypto.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* Utility class for cryptographic operations.
*
@@ -57,6 +63,36 @@ public final class CryptoUtil {
return pemKeyText
.replaceAll("-----BEGIN ((EC )|(RSA ))?(PRIVATE|PUBLIC) KEY-----", "")
.replaceAll("-----END ((EC )|(RSA ))?(PRIVATE|PUBLIC) KEY-----", "")
.replaceAll("\n", "");
.replace("\n", "");
}
/**
* Computes a Hash-based Message Authentication Code (HMAC) using the SHA-256 algorithm.
* <p>
* The input payload and secret key are both processed using the {@code UTF-8} charset
* to guarantee consistent results across different operating system environments.
* The final byte array output is converted into a lower-case hexadecimal string.
*
* @param payload the raw string data or message content to be authenticated
* @param secret the secret key used to sign the payload
* @return a lower-case hexadecimal string representing the computed HMAC-SHA256 signature
* @throws NoSuchAlgorithmException if the HmacSHA256 algorithm is not available in the environment
* @throws InvalidKeyException if the provided secret key is inappropriate for
* initialising the MAC
*/
public static String hmacSha256(
String payload,
String secret
) throws NoSuchAlgorithmException, InvalidKeyException {
var secretKeySpec = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
var mac = Mac.getInstance("HmacSHA256");
mac.init(secretKeySpec);
var rawHmac = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
return EncodingUtil.bytesToHex(rawHmac);
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024-2026 OnixByte
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.onixbyte.crypto.util;
import java.util.HexFormat;
/**
* Utility class for handling various data encoding and formatting operations.
* <p>
* This class provides helper methods to convert raw binary data into standardised
* string representations (such as hexadecimal format) commonly required for
* cryptographic verification, logging, and data transmission.
* <p>
* This utility class is stateless and thread-safe.
*
* @author siujamo
*/
public class EncodingUtil {
/**
* Converts an array of bytes into its corresponding hexadecimal string representation.
* <p>
* Each byte is converted to a two-digit hex string. If the resulting hex value
* is a single digit (i.e., less than 16), a leading '0' is automatically prepended
* to ensure a consistent and uniform format throughout the output string.
*
* @param bytes the byte array to be converted, typically the raw output of a cryptographic hash
* @return a lower-case hexadecimal string representing the input bytes
*/
public static String bytesToHex(byte[] bytes) {
return HexFormat.of().formatHex(bytes);
}
}
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package com.onixbyte.common.util;
package com.onixbyte.crypto.util;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -66,7 +66,7 @@ import java.util.Optional;
*
* @author zihluwang
* @version 3.0.0
* @see java.security.MessageDigest
* @see MessageDigest
*/
public final class HashUtil {
+6
View File
@@ -21,6 +21,12 @@
#
artefactVersion=3.3.0
commonToolboxVersion=3.3.0
tupleVersion=3.3.0
identityGeneratorVersion=3.3.0
cryptoToolboxVersion=3.3.0
mathToolboxVersion=3.3.0
versionCatalogueVersion=3.3.0
projectUrl=https://onixbyte.com/projects/onixbyte-toolbox
projectGithubUrl=https://github.com/onixbyte/onixbyte-toolbox
licenseName=MIT
+4 -2
View File
@@ -29,7 +29,9 @@ plugins {
id("signing")
}
val artefactVersion: String by project
val identityGeneratorVersion: String by project
version = identityGeneratorVersion
val projectUrl: String by project
val projectGithubUrl: String by project
val licenseName: String by project
@@ -70,7 +72,7 @@ publishing {
create<MavenPublication>("identityGenerator") {
groupId = group.toString()
artifactId = "identity-generator"
version = artefactVersion
version = identityGeneratorVersion
pom {
name = "OnixByte Identity Generator"
+4 -2
View File
@@ -29,7 +29,9 @@ plugins {
id("signing")
}
val artefactVersion: String by project
val mathToolboxVersion: String by project
version = mathToolboxVersion
val projectUrl: String by project
val projectGithubUrl: String by project
val licenseName: String by project
@@ -70,7 +72,7 @@ publishing {
create<MavenPublication>("mathToolbox") {
groupId = group.toString()
artifactId = "math-toolbox"
version = artefactVersion
version = mathToolboxVersion
pom {
name = "OnixByte Math Toolbox"
+4 -2
View File
@@ -29,7 +29,9 @@ plugins {
id("signing")
}
val artefactVersion: String by project
val tupleVersion: String by project
version = tupleVersion
val projectUrl: String by project
val projectGithubUrl: String by project
val licenseName: String by project
@@ -70,7 +72,7 @@ publishing {
create<MavenPublication>("tuple") {
groupId = group.toString()
artifactId = "tuple"
version = artefactVersion
version = tupleVersion
pom {
name = "OnixByte Tuple"
+14 -7
View File
@@ -28,7 +28,14 @@ plugins {
id("signing")
}
val artefactVersion: String by project
val commonToolboxVersion: String by project
val identityGeneratorVersion: String by project
val cryptoToolboxVersion: String by project
val mathToolboxVersion: String by project
val tupleVersion: String by project
val versionCatalogueVersion: String by project
version = versionCatalogueVersion
val projectUrl: String by project
val projectGithubUrl: String by project
val licenseName: String by project
@@ -40,11 +47,11 @@ repositories {
dependencies {
constraints {
api("com.onixbyte:common-toolbox:$artefactVersion")
api("com.onixbyte:identity-generator:$artefactVersion")
api("com.onixbyte:crypto-toolbox:$artefactVersion")
api("com.onixbyte:math-toolbox:$artefactVersion")
api("com.onixbyte:tuple:$artefactVersion")
api("com.onixbyte:common-toolbox:$commonToolboxVersion")
api("com.onixbyte:identity-generator:$identityGeneratorVersion")
api("com.onixbyte:crypto-toolbox:$cryptoToolboxVersion")
api("com.onixbyte:math-toolbox:$mathToolboxVersion")
api("com.onixbyte:tuple:$tupleVersion")
}
}
@@ -53,7 +60,7 @@ publishing {
create<MavenPublication>("versionCatalogue") {
groupId = group.toString()
artifactId = "version-catalogue"
version = artefactVersion
version = versionCatalogueVersion
pom {
name = "OnixByte Version Catalogue"