diff --git a/.github/project.yml b/.github/project.yml
index a8fcec34..89cdfd59 100644
--- a/.github/project.yml
+++ b/.github/project.yml
@@ -2,5 +2,5 @@ name: cui-java-tools
pages-reference: cui-java-tools
sonar-project-key: cuioss_cui-java-tools
release:
- current-version: 2.3.1
- next-version: 2.3-SNAPSHOT
+ current-version: 2.4.0
+ next-version: 2.4.1-SNAPSHOT
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
deleted file mode 100644
index 0f989b20..00000000
--- a/.github/workflows/codeql.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
- push:
- branches: ["main"]
- pull_request:
- # The branches below must be a subset of the branches above
- branches: ["main"]
- schedule:
- - cron: "0 0 * * 1"
-
-permissions:
- contents: read
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: ["java"]
- # CodeQL supports [ $supported-codeql-languages ]
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
-
- steps:
- - name: Harden the runner (Audit all outbound calls)
- uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
- with:
- egress-policy: audit
-
- - name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
-
- # âšī¸ Command-line programs to run using the OS shell.
- # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-
- # If the Autobuild fails above, remove it and uncomment the following three lines.
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
-
- # - run: |
- # echo "Run, Build Application using script"
- # ./location_of_script_within_repo/buildscript.sh
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
- with:
- category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml
index d945c92c..e0069837 100644
--- a/.github/workflows/maven-release.yml
+++ b/.github/workflows/maven-release.yml
@@ -31,10 +31,10 @@ jobs:
metadata-file-path: '.github/project.yml'
local-file: true
- - name: Set up JDK 17
+ - name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
- java-version: '17'
+ java-version: '21'
distribution: 'temurin'
server-id: central
server-username: MAVEN_USERNAME
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 483129b6..6c79629c 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- version: [ 17,21,24 ]
+ version: [ 21,24 ]
steps:
- name: Harden the runner (Audit all outbound calls)
@@ -45,10 +45,10 @@ jobs:
with:
fetch-depth: 0
- - name: Set up JDK 17 for Sonar-build
+ - name: Set up JDK 21 for Sonar-build
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
- java-version: '17'
+ java-version: '21'
distribution: 'temurin'
cache: maven
@@ -84,10 +84,10 @@ jobs:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - name: Set up JDK 17 for snapshot release
+ - name: Set up JDK 21 for snapshot release
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
- java-version: '17'
+ java-version: '21'
distribution: 'temurin'
server-id: central
server-username: MAVEN_USERNAME
diff --git a/doc/ai-rules.md b/doc/ai-rules.md
new file mode 100644
index 00000000..c089cdc9
--- /dev/null
+++ b/doc/ai-rules.md
@@ -0,0 +1,369 @@
+# AI Development Guidelines
+
+This file provides guidance to AI tools (IntelliJ Junie, Claude Code, GitHub Copilot, etc.) when working with code in CUI projects.
+
+## Configuration
+
+**Standards Base URL**: Configure this based on your development environment:
+- **Remote (gitingest)**: `https://gitingest.com/github.com/cuioss/cui-llm-rules/tree/main`
+- **Local checkout**: Use relative path to your local standards directory (e.g., `../cui-llm-rules`)
+
+Replace `{STANDARDS_BASE_URL}` in all references below with your chosen base URL.
+
+## Context Hierarchy and Priority Framework
+**Critical for AI System Decision Making**
+
+When conflicting information exists, AI systems must follow this priority order:
+
+1. **Core Process Rules** (CRITICAL - highest priority, non-negotiable)
+2. **Project-Specific Context** (CLAUDE.md, .github/copilot-instructions.md, local config)
+3. **Standards References** (adaptable based on context and requirements)
+4. **General Guidelines** (lowest priority, may be overridden by higher levels)
+
+### Context-Aware Response Patterns
+- **New Project Context**: Emphasize architecture decisions and initial setup standards
+- **Legacy Code Context**: Prioritize compatibility, incremental changes, and migration paths
+- **Testing Context**: Focus on coverage requirements and quality standards
+- **Documentation Context**: Emphasize clarity, completeness, and AsciiDoc standards
+- **Security Context**: Apply strictest security standards without compromise
+
+## Core Process Rules (CRITICAL - READ FIRST)
+**Reference**: `{STANDARDS_BASE_URL}/standards/process/general.adoc`
+
+These rules govern ALL development activities:
+
+### đ¨ PRE-1.0 PROJECT RULE (HIGHEST PRIORITY)
+**This project is PRE-1.0 and therefore:**
+- **NEVER deprecate code** - Remove it directly if not needed
+- **NEVER add transitional comments** like "TODO: Remove in v2.0"
+- **NEVER enforce backward compatibility** - Make breaking changes freely
+- **NEVER add @Deprecated annotations** - Delete unnecessary code immediately
+- **Clean APIs aggressively** - Remove unused methods, classes, and patterns
+- **Focus on final API design** - Design for post-1.0 stability, not pre-1.0 transitions
+
+### General Process Rules
+1. **If in doubt, ask the user** - Never make assumptions
+2. **Always research topics** - Use available tools (WebSearch, WebFetch, etc.) to find the most recent best practices
+3. **Never guess or be creative** - If you cannot find best practices, ask the user
+4. **Do not proliferate documents** - Always use context-relevant documents, never create without user approval
+5. **Never add dependencies without approval** - Always ask before adding any dependency
+
+## AI Safety and Validation Framework
+**Mandatory for All AI-Generated Content**
+
+### Safety Constraints (NON-NEGOTIABLE)
+- **Never bypass security measures**: AI must not suggest workarounds for security controls
+- **Preserve data integrity**: All changes must maintain data consistency
+- **Respect privacy**: No exposure of sensitive data in logs or outputs
+- **Maintain auditability**: All AI-generated changes must be traceable
+- **Follow standards hierarchy**: Respect the context priority framework above
+
+### Validation Requirements
+Before any code implementation:
+1. **Standards Compliance Check**: Verify output matches CUI standards
+2. **Build Verification**: Ensure generated code compiles and passes pre-commit checks
+3. **Security Review**: Check for security anti-patterns and vulnerabilities
+4. **Documentation Sync**: Verify documentation reflects any code changes
+5. **Test Coverage**: Ensure adequate test coverage for new functionality
+
+### Error Recovery Patterns
+- **Standards Violation**: Provide specific correction guidance with reference links
+- **Build Failures**: Include diagnostic steps and reference Build Commands Template
+- **Test Failures**: Guide through debugging using Testing Standards
+- **Integration Issues**: Escalate to human review with detailed context
+
+## Task Completion Standards (MANDATORY)
+**Reference**: `{STANDARDS_BASE_URL}/standards/process/task-completion-standards.adoc`
+
+### Pre-Commit Checklist
+Execute in sequence before ANY commit:
+
+1. **Quality Verification**: `./mvnw -Ppre-commit clean verify -DskipTests`
+ - Fix ALL errors and warnings (mandatory)
+ - Address code quality, formatting, and linting issues
+
+2. **Final Verification**: `./mvnw clean install`
+ - Must complete without errors or warnings
+ - All tests must pass
+ - Tasks are complete ONLY after this succeeds
+
+3. **Run Integration Tests**: `./mvnw clean verify -Pintegration-tests -pl cui-jwt-quarkus-parent/cui-jwt-quarkus-integration-tests`
+ - Ensure all integration tests pass
+ - Verify against the latest standards
+
+4. **Documentation**: Ensure all changes are documented
+ - Update Javadoc for public APIs
+ - Update AsciiDoc documentation if necessary
+
+5. **Documentation**: Update if changes affect APIs, features, or configuration
+
+6. **Commit Message**: Follow Git Commit Standards
+
+### Quality Requirements
+- New code requires appropriate test coverage
+- Existing tests must continue to pass
+- Documentation must be updated for API/feature changes
+- Link commits to relevant issues or tasks
+
+## Build Commands Template
+Common Maven commands for CUI projects:
+- Build project: `./mvnw clean install`
+- Build Single Module: `./mvnw clean install -pl
- * The default implementation uses {@link ByteBuffer#allocate(int)}. The method
- * is overridden in AllocateDirectHexTest to use
- * {@link ByteBuffer#allocateDirect(int)}
- *
- * @param capacity the capacity
- * @return the byte buffer
- */
- protected ByteBuffer allocate(final int capacity) {
- return ByteBuffer.allocate(capacity);
- }
-
private boolean charsetSanityCheck(final String name) {
final var source = "the quick brown dog jumped over the lazy fox";
try {
@@ -174,6 +160,169 @@ void encodeReadOnlyByteBuffer() {
}
}
+ @Nested
+ @DisplayName("handle static method operations")
+ class StaticMethodOperations {
+
+ @Test
+ @DisplayName("decode hex string correctly")
+ void decodeHexString() throws DecoderException {
+ final var input = "48656c6c6f20576f726c64";
+ final var expected = "Hello World".getBytes(StandardCharsets.UTF_8);
+ assertArrayEquals(expected, Hex.decodeHex(input));
+ }
+
+ @Test
+ @DisplayName("decode hex char array correctly")
+ void decodeHexCharArray() throws DecoderException {
+ final var input = "48656c6c6f20576f726c64".toCharArray();
+ final var expected = "Hello World".getBytes(StandardCharsets.UTF_8);
+ assertArrayEquals(expected, Hex.decodeHex(input));
+ }
+
+ @Test
+ @DisplayName("throw exception for odd length hex string")
+ void decodeHexOddLength() {
+ assertThrows(DecoderException.class, () -> Hex.decodeHex("ABC"));
+ assertThrows(DecoderException.class, () -> Hex.decodeHex("ABC".toCharArray()));
+ }
+
+ @Test
+ @DisplayName("throw exception for invalid hex characters")
+ void decodeHexInvalidCharacters() {
+ assertThrows(DecoderException.class, () -> Hex.decodeHex("XY"));
+ assertThrows(DecoderException.class, () -> Hex.decodeHex("XY".toCharArray()));
+ }
+
+ @Test
+ @DisplayName("encode byte array to hex chars")
+ void encodeHexByteArray() {
+ final var input = "Hello World".getBytes(StandardCharsets.UTF_8);
+ final var result = Hex.encodeHex(input);
+ final var expected = "48656c6c6f20576f726c64".toCharArray();
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ @DisplayName("encode byte buffer to hex chars")
+ void encodeHexByteBuffer() {
+ final var input = "Hello World".getBytes(StandardCharsets.UTF_8);
+ final var buffer = ByteBuffer.wrap(input);
+ final var result = Hex.encodeHex(buffer);
+ final var expected = "48656c6c6f20576f726c64".toCharArray();
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ @DisplayName("encode byte array with case control")
+ void encodeHexByteArrayWithCase() {
+ final var input = new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF};
+
+ final var lowerResult = Hex.encodeHex(input, true);
+ assertArrayEquals("abcdef".toCharArray(), lowerResult);
+
+ final var upperResult = Hex.encodeHex(input, false);
+ assertArrayEquals("ABCDEF".toCharArray(), upperResult);
+ }
+
+ @Test
+ @DisplayName("encode byte buffer with case control")
+ void encodeHexByteBufferWithCase() {
+ final var input = new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF};
+ final var buffer = ByteBuffer.wrap(input);
+
+ final var lowerResult = Hex.encodeHex(buffer, true);
+ assertArrayEquals("abcdef".toCharArray(), lowerResult);
+
+ buffer.rewind();
+ final var upperResult = Hex.encodeHex(buffer, false);
+ assertArrayEquals("ABCDEF".toCharArray(), upperResult);
+ }
+
+ @Test
+ @DisplayName("encode hex string from byte array")
+ void encodeHexStringByteArray() {
+ final var input = "Hello World".getBytes(StandardCharsets.UTF_8);
+ final var result = Hex.encodeHexString(input);
+ assertEquals("48656c6c6f20576f726c64", result);
+ }
+
+ @Test
+ @DisplayName("encode hex string from byte array with case control")
+ void encodeHexStringByteArrayWithCase() {
+ final var input = new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF};
+
+ final var lowerResult = Hex.encodeHexString(input, true);
+ assertEquals("abcdef", lowerResult);
+
+ final var upperResult = Hex.encodeHexString(input, false);
+ assertEquals("ABCDEF", upperResult);
+ }
+
+ @Test
+ @DisplayName("encode hex string from byte buffer")
+ void encodeHexStringByteBuffer() {
+ final var input = "Hello World".getBytes(StandardCharsets.UTF_8);
+ final var buffer = ByteBuffer.wrap(input);
+ final var result = Hex.encodeHexString(buffer);
+ assertEquals("48656c6c6f20576f726c64", result);
+ }
+
+ @Test
+ @DisplayName("encode hex string from byte buffer with case control")
+ void encodeHexStringByteBufferWithCase() {
+ final var input = new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF};
+ final var buffer = ByteBuffer.wrap(input);
+
+ final var lowerResult = Hex.encodeHexString(buffer, true);
+ assertEquals("abcdef", lowerResult);
+
+ buffer.rewind();
+ final var upperResult = Hex.encodeHexString(buffer, false);
+ assertEquals("ABCDEF", upperResult);
+ }
+
+ @Test
+ @DisplayName("handle empty inputs")
+ void handleEmptyInputs() throws DecoderException {
+ // Empty string decode
+ assertArrayEquals(new byte[0], Hex.decodeHex(""));
+ assertArrayEquals(new byte[0], Hex.decodeHex(new char[0]));
+
+ // Empty byte array encode
+ assertArrayEquals(new char[0], Hex.encodeHex(new byte[0]));
+ assertEquals("", Hex.encodeHexString(new byte[0]));
+
+ // Empty byte buffer encode
+ final var emptyBuffer = ByteBuffer.allocate(0);
+ assertArrayEquals(new char[0], Hex.encodeHex(emptyBuffer));
+
+ emptyBuffer.rewind();
+ assertEquals("", Hex.encodeHexString(emptyBuffer));
+ }
+ }
+
+ @Nested
+ @DisplayName("handle constructor operations")
+ class ConstructorOperations {
+
+ @Test
+ @DisplayName("create with default constructor")
+ void defaultConstructor() {
+ final var hex = new Hex();
+ assertEquals(StandardCharsets.UTF_8, hex.getCharset());
+ }
+
+ @Test
+ @DisplayName("verify toString method")
+ void toStringMethod() {
+ final var hex = new Hex();
+ final var result = hex.toString();
+ // Just verify it doesn't throw and contains class name
+ assertTrue(result.contains("Hex"));
+ }
+ }
+
@Nested
@DisplayName("handle round trip operations")
class RoundTripOperations {
diff --git a/src/test/java/de/cuioss/tools/io/FilenameUtilsTest.java b/src/test/java/de/cuioss/tools/io/FilenameUtilsTest.java
index 06920c8a..f2bbece8 100644
--- a/src/test/java/de/cuioss/tools/io/FilenameUtilsTest.java
+++ b/src/test/java/de/cuioss/tools/io/FilenameUtilsTest.java
@@ -1070,4 +1070,73 @@ void isExtensionCollection() {
assertFalse(FilenameUtils.isExtension("a.b\\file.txt", new ArrayList<>(List.of("TXT"))));
assertFalse(FilenameUtils.isExtension("a.b\\file.txt", new ArrayList<>(Arrays.asList("TXT", "RTF"))));
}
+
+ @Test
+ void directoryContains() {
+ // Test null parent - should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, () -> FilenameUtils.directoryContains(null, "/child"));
+ assertThrows(IllegalArgumentException.class, () -> FilenameUtils.directoryContains(null, null));
+
+ // Test null child - should return false
+ assertFalse(FilenameUtils.directoryContains("/parent", null));
+
+ // Test same paths - should return false (directory does not contain itself)
+ assertFalse(FilenameUtils.directoryContains("/parent", "/parent"));
+ assertFalse(FilenameUtils.directoryContains("/parent/", "/parent/"));
+ assertFalse(FilenameUtils.directoryContains("/parent", "/parent/"));
+ assertFalse(FilenameUtils.directoryContains("/parent/", "/parent"));
+
+ // Test normal containment cases - Unix style paths
+ assertTrue(FilenameUtils.directoryContains("/parent", "/parent/child"));
+ assertTrue(FilenameUtils.directoryContains("/parent", "/parent/child/grandchild"));
+ assertTrue(FilenameUtils.directoryContains("/parent/", "/parent/child"));
+ assertTrue(FilenameUtils.directoryContains("/parent", "/parent/child/"));
+ assertTrue(FilenameUtils.directoryContains("/parent/", "/parent/child/"));
+
+ // Test normal containment cases - Windows style paths
+ assertTrue(FilenameUtils.directoryContains("C:\\parent", "C:\\parent\\child"));
+ assertTrue(FilenameUtils.directoryContains("C:\\parent", "C:\\parent\\child\\grandchild"));
+ assertTrue(FilenameUtils.directoryContains("C:\\parent\\", "C:\\parent\\child"));
+ assertTrue(FilenameUtils.directoryContains("C:\\parent", "C:\\parent\\child\\"));
+ assertTrue(FilenameUtils.directoryContains("C:\\parent\\", "C:\\parent\\child\\"));
+
+ // Test non-containment cases
+ assertFalse(FilenameUtils.directoryContains("/parent", "/other"));
+ assertFalse(FilenameUtils.directoryContains("/parent", "/parent2"));
+ assertFalse(FilenameUtils.directoryContains("/parent", "/parentchild"));
+ assertFalse(FilenameUtils.directoryContains("/parent/child", "/parent"));
+ assertFalse(FilenameUtils.directoryContains("/parent/child", "/parent/sibling"));
+
+ // Test mixed separators
+ assertTrue(FilenameUtils.directoryContains("/parent", "/parent\\child"));
+ assertTrue(FilenameUtils.directoryContains("C:\\parent", "C:\\parent/child"));
+
+ // Test relative paths
+ assertTrue(FilenameUtils.directoryContains("parent", "parent/child"));
+ assertTrue(FilenameUtils.directoryContains("parent", "parent\\child"));
+ assertFalse(FilenameUtils.directoryContains("parent", "parent"));
+ assertFalse(FilenameUtils.directoryContains("parent", "other"));
+
+ // Test empty strings
+ assertFalse(FilenameUtils.directoryContains("", "child"));
+ assertFalse(FilenameUtils.directoryContains("parent", ""));
+ assertFalse(FilenameUtils.directoryContains("", ""));
+
+ // Test root directory cases
+ assertTrue(FilenameUtils.directoryContains("/", "/child"));
+ assertTrue(FilenameUtils.directoryContains("C:\\", "C:\\child"));
+ assertFalse(FilenameUtils.directoryContains("/", "/"));
+ assertFalse(FilenameUtils.directoryContains("C:\\", "C:\\"));
+
+ // Test complex paths
+ assertTrue(FilenameUtils.directoryContains("/home/user", "/home/user/documents"));
+ assertTrue(FilenameUtils.directoryContains("/home/user", "/home/user/documents/file.txt"));
+ assertFalse(FilenameUtils.directoryContains("/home/user", "/home/other"));
+ assertFalse(FilenameUtils.directoryContains("/home/user", "/home/username"));
+
+ // Test UNC paths (Windows network paths)
+ assertTrue(FilenameUtils.directoryContains("\\\\server\\share", "\\\\server\\share\\folder"));
+ assertFalse(FilenameUtils.directoryContains("\\\\server\\share", "\\\\server\\share"));
+ assertFalse(FilenameUtils.directoryContains("\\\\server\\share", "\\\\other\\share\\folder"));
+ }
}
diff --git a/src/test/java/de/cuioss/tools/io/MorePathsTest.java b/src/test/java/de/cuioss/tools/io/MorePathsTest.java
index e9ff53df..53f067ae 100644
--- a/src/test/java/de/cuioss/tools/io/MorePathsTest.java
+++ b/src/test/java/de/cuioss/tools/io/MorePathsTest.java
@@ -167,10 +167,10 @@ void shouldBackupExistingFile() throws IOException {
final List