diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index ebd6bc6d..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,169 +0,0 @@ -# SPeCS Java Libraries - Copilot Instructions - -## Repository Overview - -This repository contains 24 Java libraries developed by the SPeCS research group, organized as independent Gradle projects. Each library extends or enhances existing Java libraries (indicated by the "-Plus" suffix) or provides custom utilities. The repository uses Java 17, Gradle for builds, and follows a multi-project structure without a root Gradle configuration. - -**Repository Statistics:** -- 24 main Java libraries + 1 legacy project (SpecsHWUtils) -- ~200K+ lines of Java code across all projects -- Mixed testing frameworks: JUnit 5 (modern projects) and JUnit 4 (legacy projects) -- Inter-project dependencies centered around SpecsUtils as the core utility library - -## Build Instructions - -### Prerequisites -- **Java 17 or higher** (REQUIRED - all projects target Java 17) -- **Gradle 8.13+** (system Gradle installation - NO gradlew wrapper in repo) - -### Building Individual Projects - -**ALWAYS navigate to the specific project directory before running Gradle commands.** Each project is self-contained with its own `build.gradle` and `settings.gradle`. - -```bash -cd -gradle build # Compile, test, package, and validate coverage (includes tests) -gradle test # Run tests only (without packaging) -gradle jar # Create JAR file -gradle clean # Clean build artifacts -``` - -### Build Dependencies Order - -Due to inter-project dependencies, build in this order when building multiple projects: - -1. **Core Libraries (no dependencies):** - - CommonsLangPlus, CommonsCompressPlus, GsonPlus, XStreamPlus - -2. **SpecsUtils** (most projects depend on this) - -3. **All other projects** (depend on SpecsUtils and/or other core libraries) - -### Key Build Considerations - -- **SpecsUtils tests are slow** - can take several minutes to complete -- **`gradle build` includes tests** - build will fail if tests fail or coverage is insufficient -- Projects use **different testing frameworks**: - - Modern projects: JUnit 5 with Mockito, AssertJ + Jacoco coverage validation - - Legacy projects: JUnit 4 -- **Coverage requirements**: Modern projects with Jacoco enforce 80% minimum test coverage -- **No parallel builds** - run projects sequentially to avoid dependency conflicts -- **Inter-project dependencies** use syntax like `implementation ':SpecsUtils'` - -### Common Build Issues & Solutions - -1. **Dependency not found errors**: Ensure dependent projects are built first (e.g., build SpecsUtils before projects that depend on it) -2. **Java version errors**: Verify Java 17+ is active (`java -version`) -3. **Test timeouts**: SpecsUtils tests can take 5+ minutes - be patient -4. **Coverage failures**: Modern projects require 80% test coverage - add tests if build fails due to insufficient coverage -5. **Memory issues**: For large projects, use `gradle build -Xmx2g` if needed - -## Project Layout - -### Root Structure -``` -├── .github/workflows/nightly.yml # CI pipeline -├── README.md # Project documentation -├── LICENSE # Apache 2.0 license -├── .gitignore # Ignores build/, .gradle/, etc. -└── [24 Java library directories]/ -``` - -### Individual Project Structure -``` -ProjectName/ -├── build.gradle # Gradle build configuration -├── settings.gradle # Project settings -├── src/ # Main source code -├── test/ # Unit tests (JUnit 4 or 5) -├── resources/ # Resources (optional) -├── bin/ # Eclipse-generated (ignore) -└── build/ # Generated build artifacts -``` - -### Key Libraries and Their Purpose - -**Core Infrastructure:** -- **SpecsUtils** - Core utilities, most other projects depend on this -- **CommonsLangPlus** - Extended Apache Commons Lang utilities -- **jOptions** - Command-line options and configuration management - -**External Integrations:** -- **GitPlus** - Git operations and utilities -- **GitlabPlus** - GitLab API integration -- **SlackPlus** - Slack API integration -- **JsEngine** - JavaScript execution via GraalVM - -**Data Processing:** -- **GsonPlus** - Extended JSON processing with Google Gson -- **JacksonPlus** - Extended JSON processing with Jackson -- **XStreamPlus** - Extended XML processing - -**Development Tools:** -- **JavaGenerator** - Java code generation utilities -- **AntTasks** - Custom Ant build tasks - -### Legacy Projects (No Gradle builds) -- **SpecsHWUtils** - Hardware utilities (Eclipse project only) - -## Continuous Integration - -### GitHub Actions Workflow -File: `.github/workflows/nightly.yml` - -**Triggers:** Push to any branch, manual workflow dispatch -**Environment:** Ubuntu latest, Java 17 (Temurin), Gradle current - -**Build Process:** -1. Sequentially builds and tests all 24 Gradle projects -2. Uses `gradle build test` for each project -3. Fails if any project fails to build or test -4. Publishes JUnit test reports -5. Generates dependency graphs - -### Tested Projects (in CI order): -AntTasks, AsmParser, CommonsCompressPlus, CommonsLangPlus, GearmanPlus, GitlabPlus, GitPlus, Gprofer, GsonPlus, GuiHelper, JacksonPlus, JadxPlus, JavaGenerator, jOptions, JsEngine, LogbackPlus, MvelPlus, SlackPlus, SpecsUtils, SymjaPlus, tdrcLibrary, XStreamPlus - -### Local Validation Steps -1. **Build specific project**: `cd ProjectName && gradle build` -2. **Run tests**: `cd ProjectName && gradle test` -3. **Check code coverage** (for projects with Jacoco): `gradle jacocoTestReport` -4. **Validate dependencies**: Ensure dependent projects build successfully - -## Development Guidelines - -### Code Style & Conventions -- Java 17 language features are preferred -- Follow existing patterns within each project -- Add tests for new functionality (JUnit 5 for new code) -- Use appropriate testing framework for the project (check existing tests) - -### Testing Approach -- **Modern projects**: JUnit 5 + Mockito + AssertJ with Jacoco coverage enforcement (80% minimum) -- **Legacy projects**: JUnit 4 -- **Coverage validation**: Jacoco runs automatically with tests and enforces minimum coverage thresholds -- **Test locations**: Tests in `test/` directory, following package structure - -### Making Changes -1. Identify the correct project for your changes -2. Check project's testing framework and conventions -3. Build the project first: `cd ProjectName && gradle build` -4. Make changes following existing patterns -5. Add/update tests appropriately -6. Re-run `gradle build` to ensure everything works -7. For projects with dependencies, test dependent projects as well - -### Key Files to Check -- `build.gradle` - Dependencies, Java version, testing framework -- `src/` - Main source code structure and patterns -- `test/` - Testing approach and existing test structure -- `README.md` (if present) - Project-specific documentation - -## Trust These Instructions - -These instructions are comprehensive and validated. Only search for additional information if: -1. A specific command fails with an unexpected error -2. You encounter a build configuration not covered here -3. Project-specific documentation contradicts these general guidelines - -Always check the project's individual `build.gradle` for dependencies and testing setup before making changes. diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f6064405..5023bbb4 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout specs-java-libs - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Java uses: actions/setup-java@v4 diff --git a/JavaGenerator/src/org/specs/generators/java/types/JavaType.java b/JavaGenerator/src/org/specs/generators/java/types/JavaType.java index 1b4ffa14..6a82b676 100644 --- a/JavaGenerator/src/org/specs/generators/java/types/JavaType.java +++ b/JavaGenerator/src/org/specs/generators/java/types/JavaType.java @@ -288,11 +288,7 @@ public void setArrayDimension(int arrayDimension) { @Override public String toString() { - String toString = (hasPackage() ? _package + "." : "") + name; - if (isArray()) { - toString += "[]".repeat(arrayDimension); - } - return toString; + return getCanonicalType(); } /** diff --git a/SpecsUtils/src/pt/up/fe/specs/util/SpecsCollections.java b/SpecsUtils/src/pt/up/fe/specs/util/SpecsCollections.java index 0911fc6e..9a75b48e 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/SpecsCollections.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/SpecsCollections.java @@ -19,6 +19,7 @@ import java.lang.reflect.Array; import java.util.*; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -320,6 +321,10 @@ public static T[] cast(Object[] array, Class targetClass) { .toArray(i -> (T[]) Array.newInstance(targetClass, i)); } + public static T[] cast(Object[] array, IntFunction arrayFactory) { + return Arrays.stream(array).toArray(arrayFactory); + } + /** * Casts a list of one type to another type, without checking if the elements * can be cast to the target type. diff --git a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java index 76d0558e..2599f147 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java @@ -41,6 +41,14 @@ public BiFunctionClassMap() { this.classMapper = new ClassMapper(); } + public BiFunctionClassMap(BiFunctionClassMap other) { + this.map = new HashMap<>(); + for (var keyPair : other.map.entrySet()) { + this.map.put(keyPair.getKey(), (BiFunction) keyPair.getValue()); + } + this.classMapper = new ClassMapper(other.classMapper); + } + /** * Associates the specified value with the specified key. * diff --git a/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java b/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java index 46d4bb11..d6d62d0f 100644 --- a/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java +++ b/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java @@ -42,6 +42,108 @@ void testDefaultConstructor() { assertThatThrownBy(() -> map.apply(new Object(), "test")) .hasMessageContaining("BiConsumer not defined for class"); } + + @Test + @DisplayName("Should create copy with all mappings from original") + void testCopyConstructor() { + numberMap.put(Integer.class, (i, s) -> "Int: " + i + "-" + s); + numberMap.put(Double.class, (d, s) -> "Double: " + d + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + assertThat(copy.apply(42, "test")).isEqualTo("Int: 42-test"); + assertThat(copy.apply(3.14, "test")).isEqualTo("Double: 3.14-test"); + } + + @Test + @DisplayName("Should create independent copy - changes to original don't affect copy") + void testCopyIndependenceOriginalToNew() { + numberMap.put(Integer.class, (i, s) -> "Original: " + i + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + // Modify original + numberMap.put(Integer.class, (i, s) -> "Modified: " + i + "-" + s); + numberMap.put(Double.class, (d, s) -> "New: " + d + "-" + s); + + // Copy should retain original behavior + assertThat(copy.apply(42, "test")).isEqualTo("Original: 42-test"); + + // Copy shouldn't have the new Double mapping + assertThatThrownBy(() -> copy.apply(3.14, "test")) + .hasMessageContaining("BiConsumer not defined for class"); + } + + @Test + @DisplayName("Should create independent copy - changes to copy don't affect original") + void testCopyIndependenceNewToOriginal() { + numberMap.put(Integer.class, (i, s) -> "Original: " + i + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + // Modify copy + copy.put(Integer.class, (i, s) -> "Modified: " + i + "-" + s); + copy.put(Double.class, (d, s) -> "New: " + d + "-" + s); + + // Original should retain original behavior + assertThat(numberMap.apply(42, "test")).isEqualTo("Original: 42-test"); + + // Original shouldn't have the new Double mapping + assertThatThrownBy(() -> numberMap.apply(3.14, "test")) + .hasMessageContaining("BiConsumer not defined for class"); + } + + @Test + @DisplayName("Should copy empty map successfully") + void testCopyEmptyMap() { + BiFunctionClassMap emptyMap = new BiFunctionClassMap<>(); + BiFunctionClassMap copy = new BiFunctionClassMap<>(emptyMap); + + assertThatThrownBy(() -> copy.apply(42, "test")) + .hasMessageContaining("BiConsumer not defined for class"); + } + + @Test + @DisplayName("Should copy classMapper for hierarchy resolution") + void testCopyClassMapper() { + numberMap.put(Number.class, (n, s) -> "Number: " + n + "-" + s); + numberMap.put(Integer.class, (i, s) -> "Integer: " + i + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + // Hierarchy resolution should work in copy + assertThat(copy.apply(42, "test")).isEqualTo("Integer: 42-test"); + assertThat(copy.apply(3.14, "test")).isEqualTo("Number: 3.14-test"); + assertThat(copy.apply(42L, "test")).isEqualTo("Number: 42-test"); + } + + @Test + @DisplayName("Should handle covariant return type in copy constructor") + void testCovariantCopy() { + BiFunctionClassMap intResultMap = new BiFunctionClassMap<>(); + intResultMap.put(Integer.class, (i, s) -> s.length() + i.intValue()); + + // Copy with covariant return type (Integer extends Number) + BiFunctionClassMap copy = new BiFunctionClassMap<>(intResultMap); + + Number result = copy.apply(10, "test"); + assertThat(result).isEqualTo(14); + } + + @Test + @DisplayName("Should copy map with multiple hierarchy levels") + void testCopyComplexHierarchy() { + BiFunctionClassMap exceptionMap = new BiFunctionClassMap<>(); + exceptionMap.put(Exception.class, (e, s) -> "Exception: " + s); + exceptionMap.put(RuntimeException.class, (e, s) -> "Runtime: " + s); + exceptionMap.put(IllegalArgumentException.class, (e, s) -> "IllegalArg: " + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(exceptionMap); + + assertThat(copy.apply(new Exception(), "test")).isEqualTo("Exception: test"); + assertThat(copy.apply(new RuntimeException(), "test")).isEqualTo("Runtime: test"); + assertThat(copy.apply(new IllegalArgumentException(), "test")).isEqualTo("IllegalArg: test"); + } } @Nested