diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c1cc8abb..9493d6f2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -51,6 +51,11 @@ Common pitfalls and fixes Linting & style - The root does not expose an obvious global formatting tool (no Spotless or root Checkstyle detected). Use the existing code style. Run `./gradlew check` to execute configured verification tasks. +Collections optimization pattern +- In collection implementations (dictionaries, arrays), use early-exit `isEmpty()` checks to avoid unnecessary allocations and iterations. Return empty collections or unmodified containers immediately when the collection is empty. +- Example: Check `isEmpty()` before allocating arrays in `keys()`, `values()`, or `iterator()` methods; return `IntArray.empty()`, `Array.empty(type)`, or unmodified containers on empty state. +- This applies across all collection types: `IntToRef`, `LongToRef`, `RefToRef` dictionaries, and array implementations. + Testing conventions - Use AssertJ for assertions (import `org.assertj.core.api.Assertions`), not JUnit's `Assertions`. - Test class naming: `Test.java` in the same package under `src/test/java`. @@ -68,7 +73,7 @@ Testing conventions Javadoc conventions - All public classes, interfaces, and methods should have javadoc. -- Javadoc must include `@since` tag with version (e.g., `@since 10.0.0`). +- Javadoc must include `@since` tag with version (e.g., `@since 10.0.0`). Use the base GA version, not pre-release qualifiers (e.g., use `10.0.0` not `10.0.alpha14`), even if `build.gradle` specifies a pre-release version. - Use short, active-voice descriptions (e.g., "Returns an array" not "Return an array"). - Do not use trailing periods in `@param` and `@return` descriptions (e.g., `@param value the value` not `@param value the value.`). - Include `@param` and `@return` tags for non-trivial methods. diff --git a/.github/skills/generate-branch-summary.md b/.github/skills/generate-branch-summary.md new file mode 100644 index 00000000..564dff92 --- /dev/null +++ b/.github/skills/generate-branch-summary.md @@ -0,0 +1,163 @@ +# Skill: Generate Branch Summary + +## Purpose +Generate a concise, structured summary of all changes in the current branch compared to the develop branch. This summary is written to `summary.md` and serves as the single source of truth for understanding what a branch introduces. + +## When to Use +- After completing implementation work on a feature branch +- Before opening a pull request to develop +- When updating branch documentation +- To communicate changes clearly to reviewers + +## Process + +### 1. Determine Current Branch Context +```bash +git rev-parse --abbrev-ref HEAD # Get current branch name +git log --oneline -5 # Check recent commits +``` + +### 2. Analyze Changes Relative to Develop +```bash +git diff --stat develop...HEAD # Summary of changed files +git log --oneline develop..HEAD # Commits in this branch +git --no-pager diff develop...HEAD # Full diff (use --no-pager to avoid pager issues) +``` + +### 3. Categorize Changes +Group modifications by their nature: +- **New APIs/Features** — new interfaces, classes, methods +- **Performance Optimizations** — efficiency improvements, early-exit checks, reduced allocations +- **Bug Fixes** — corrections to existing behavior +- **Refactoring** — internal improvements without behavior change +- **Documentation** — javadoc, comments, instruction files, style guides +- **Version Bumps** — changes to version numbers in build.gradle or other config + +### 4. Structure the Summary +Create a section in `summary.md` with this format: + +```markdown +# Branch: + +**Status:** +**Commits:** (e.g., "1 (abc1234 - 'commit message')" or "3 commits") +**Version:** (if applicable) + +## Changes Summary + +### (e.g., "New Array API") +- Description of what was added/changed +- Key implementation details +- Important behaviors or constraints + +### (e.g., "Collection Optimization Pattern") +- Explanation of the optimization +- Which files/classes affected +- Methods modified (count if many) + +### (e.g., "Documentation") +- What documentation was added +- Why it matters (e.g., for future contributors) + +## Testing & Validation +- `./gradlew :module-name:build` ✅ +- `./gradlew clean build` (recommended before merge) +``` + +### 5. Key Guidelines for Content + +#### Be Specific +- ✅ "Added `tryTrimTo(int)` method to safely resize internal storage" +- ❌ "Added new method" + +#### Quantify When Helpful +- ✅ "Applied isEmpty() checks to 21 methods across 3 dictionary types" +- ❌ "Applied optimization across dictionaries" + +#### Explain Impact +- ✅ "Avoids unnecessary allocations and iterations when collections are empty" +- ❌ "Performance improvement" + +#### Include Implementation Details +- ✅ "Returns unchanged if requested size exceeds capacity or is less than current size" +- ❌ "Has validation logic" + +#### Use Clear Formatting +- Use markdown headers (`#`, `##`, `###`) for hierarchy +- Use bullet points for lists +- Use bold (`**`) for emphasis on key terms +- Use inline code (`` ` ` ``) for class/method names + +### 6. Output Location +Always write to `summary.md` in the repository root, replacing or updating the previous summary section. + +### 7. Keep Previous Content +If `summary.md` already contains unrelated sections (e.g., documentation of other work), preserve them unless explicitly asked to remove them. Only update or replace the branch summary section. + +## Example Output + +```markdown +# Branch: update-api-part-2 + +**Status:** Ready for review +**Commits:** 1 (1367d9b - "small improvements") +**Version:** 10.0.alpha13 → 10.0.alpha14 + +## Changes Summary + +### 1. New Array API: `tryTrimTo(int)` +- Added `UnsafeMutableArray.tryTrimTo(int internalStorageSize)` method for safe internal storage resizing +- Implemented in `AbstractMutableArray` with validation: + - Returns unchanged if requested size exceeds current capacity + - Returns unchanged if requested size is less than current element count + - Performs trimming for valid requests +- Includes complete javadoc with `@since 10.0.alpha14` tag + +### 2. Collection Optimization Pattern +Applied early-exit `isEmpty()` checks across dictionary implementations: +- **AbstractHashBasedIntToRefDictionary:** 7 methods optimized +- **AbstractHashBasedLongToRefDictionary:** 7 methods optimized +- **AbstractHashBasedRefToRefDictionary:** 7 methods optimized + +Optimized methods: `iterator()`, `keys()` (all overloads), `values()` (all overloads) +**Benefit:** Avoids allocations and iterations when collections are empty + +### 3. Documentation +- Added comprehensive javadoc to `tryTrimTo()` method +- Updated `.github/copilot-instructions.md` with "Collections optimization pattern" section + +## Testing & Validation +- Module build: `./gradlew :rlib-collections:build` ✅ +- Full build verification: `./gradlew clean build` (recommended before merge) +``` + +## Common Patterns in RLib + +### API Additions +Always mention: +- Method signature and purpose +- Return type and parameter descriptions +- Javadoc status (whether included, version tag) +- Key behaviors or constraints + +### Optimizations +Always mention: +- What was optimized (method names, counts) +- Why (avoid allocations, reduce iterations, etc.) +- Affected files/classes +- Observable impact + +### Documentation Changes +Mention: +- What was added or updated +- Why it matters (helps future contributors, clarifies usage, standardizes conventions) +- Files affected + +## Tips + +1. **Use `git diff --stat`** to get a quick overview of which files changed and how many lines +2. **Use `git log develop..HEAD`** to list all commits in the branch for reference +3. **Extract key insights from the actual diff** — skim the output to understand the nature of changes +4. **Be concise** — summaries should be readable in 2-3 minutes +5. **Link to code** — mention file paths and method names so reviewers can navigate easily +6. **Quantify changes** — "7 methods optimized" is more informative than "several optimizations" diff --git a/README.md b/README.md index 4f0133c4..a1422fc5 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ repositories { } ext { - rlibVersion = "10.0.alpha13" + rlibVersion = "10.0.alpha14" } dependencies { diff --git a/build.gradle b/build.gradle index 93de4b81..e720f454 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -rootProject.version = "10.0.alpha13" +rootProject.version = "10.0.alpha14" group = 'javasabr.rlib' allprojects { diff --git a/rlib-collections/src/main/java/javasabr/rlib/collections/array/UnsafeMutableArray.java b/rlib-collections/src/main/java/javasabr/rlib/collections/array/UnsafeMutableArray.java index ff2297c7..2319fe3e 100644 --- a/rlib-collections/src/main/java/javasabr/rlib/collections/array/UnsafeMutableArray.java +++ b/rlib-collections/src/main/java/javasabr/rlib/collections/array/UnsafeMutableArray.java @@ -53,4 +53,16 @@ public interface UnsafeMutableArray extends UnsafeArray, MutableArray { * @since 10.0.0 */ UnsafeMutableArray trimToSize(); + + /** + * Attempts to trim the internal storage to the specified size. + * + * If the requested size is greater than the current capacity or less than the + * current size, the array is not modified. + * + * @param internalStorageSize the desired internal storage size + * @return this for method chaining + * @since 10.0.0 + */ + UnsafeMutableArray tryTrimTo(int internalStorageSize); } diff --git a/rlib-collections/src/main/java/javasabr/rlib/collections/array/impl/AbstractMutableArray.java b/rlib-collections/src/main/java/javasabr/rlib/collections/array/impl/AbstractMutableArray.java index d462190d..854eb54a 100644 --- a/rlib-collections/src/main/java/javasabr/rlib/collections/array/impl/AbstractMutableArray.java +++ b/rlib-collections/src/main/java/javasabr/rlib/collections/array/impl/AbstractMutableArray.java @@ -214,7 +214,6 @@ public UnsafeMutableArray prepareForSize(int expectedSize) { public UnsafeMutableArray trimToSize() { @Nullable E[] wrapped = wrapped(); int size = size(); - if (size == wrapped.length) { return this; } @@ -222,6 +221,19 @@ public UnsafeMutableArray trimToSize() { return this; } + @Override + public UnsafeMutableArray tryTrimTo(int internalStorageSize) { + @Nullable E[] wrapped = wrapped(); + int size = size(); + if (internalStorageSize >= wrapped.length) { + return this; + } else if (internalStorageSize < size) { + return this; + } + wrapped(Arrays.copyOf(wrapped, internalStorageSize)); + return this; + } + @Override public UnsafeMutableArray asUnsafe() { return this; diff --git a/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedIntToRefDictionary.java b/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedIntToRefDictionary.java index d8da55cf..debd484b 100644 --- a/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedIntToRefDictionary.java +++ b/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedIntToRefDictionary.java @@ -85,8 +85,9 @@ public Optional getOptional(int key) { public Iterator iterator() { if (isEmpty()) { return Collections.emptyIterator(); + } else { + return new LinkedRefEntryIterator<>(entries()); } - return new LinkedRefEntryIterator<>(entries()); } @Nullable @@ -118,48 +119,59 @@ public void forEach(IntObjConsumer consumer) { @Override public IntArray keys() { - return IntArray.copyOf(keys(ArrayFactory.mutableIntArray())); + if (isEmpty()) { + return IntArray.empty(); + } else { + return IntArray.copyOf(keys(ArrayFactory.mutableIntArray())); + } } @Override public MutableIntArray keys(MutableIntArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableIntArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { unsafe.unsafeAdd(entry.key()); entry = entry.next(); } } - return container; } @Override public Array keys(Class type) { - return keys(MutableArray.ofType(Integer.class)); + if (isEmpty()) { + return Array.empty(type); + } else { + return keys(MutableArray.ofType(type)); + } } @Override public MutableArray keys(MutableArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { unsafe.unsafeAdd(entry.key()); entry = entry.next(); } } - return container; } @Override public > C keys(C container) { + if (isEmpty()) { + return container; + } for (E entry : entries()) { while (entry != null) { container.add(entry.key()); @@ -171,11 +183,18 @@ public > C keys(C container) { @Override public Array values(Class type) { - return Array.copyOf(values(ArrayFactory.mutableArray(type, size()))); + if (isEmpty()) { + return Array.empty(type); + } else { + return Array.copyOf(values(ArrayFactory.mutableArray(type, size()))); + } } @Override public > C values(C container) { + if (isEmpty()) { + return container; + } for (E entry : entries()) { while (entry != null) { V value = entry.value(); @@ -190,10 +209,11 @@ public > C values(C container) { @Override public MutableArray values(MutableArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { V value = entry.value(); @@ -203,7 +223,6 @@ public MutableArray values(MutableArray container) { entry = entry.next(); } } - return container; } } diff --git a/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedLongToRefDictionary.java b/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedLongToRefDictionary.java index 2b5dcb45..07f2937a 100644 --- a/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedLongToRefDictionary.java +++ b/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedLongToRefDictionary.java @@ -85,8 +85,9 @@ public Optional getOptional(long key) { public Iterator iterator() { if (isEmpty()) { return Collections.emptyIterator(); + } else { + return new LinkedRefEntryIterator<>(entries()); } - return new LinkedRefEntryIterator<>(entries()); } @Nullable @@ -118,48 +119,59 @@ public void forEach(LongObjConsumer consumer) { @Override public LongArray keys() { - return LongArray.copyOf(keys(ArrayFactory.mutableLongArray())); + if (isEmpty()) { + return LongArray.empty(); + } else { + return LongArray.copyOf(keys(ArrayFactory.mutableLongArray())); + } } @Override public MutableLongArray keys(MutableLongArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableLongArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { unsafe.unsafeAdd(entry.key()); entry = entry.next(); } } - return container; } @Override public Array keys(Class type) { - return keys(MutableArray.ofType(Long.class)); + if (isEmpty()) { + return Array.empty(type); + } else { + return keys(MutableArray.ofType(type)); + } } @Override public MutableArray keys(MutableArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { unsafe.unsafeAdd(entry.key()); entry = entry.next(); } } - return container; } @Override public > C keys(C container) { + if (isEmpty()) { + return container; + } for (E entry : entries()) { while (entry != null) { container.add(entry.key()); @@ -171,11 +183,18 @@ public > C keys(C container) { @Override public Array values(Class type) { - return Array.copyOf(values(ArrayFactory.mutableArray(type, size()))); + if (isEmpty()) { + return Array.empty(type); + } else { + return Array.copyOf(values(ArrayFactory.mutableArray(type, size()))); + } } @Override public > C values(C container) { + if (isEmpty()) { + return container; + } for (E entry : entries()) { while (entry != null) { V value = entry.value(); @@ -190,10 +209,11 @@ public > C values(C container) { @Override public MutableArray values(MutableArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { V value = entry.value(); @@ -203,7 +223,6 @@ public MutableArray values(MutableArray container) { entry = entry.next(); } } - return container; } } diff --git a/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedRefToRefDictionary.java b/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedRefToRefDictionary.java index 4e3bc5f6..4771b91d 100644 --- a/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedRefToRefDictionary.java +++ b/rlib-collections/src/main/java/javasabr/rlib/collections/dictionary/impl/AbstractHashBasedRefToRefDictionary.java @@ -62,8 +62,9 @@ public Optional getOptional(K key) { public Iterator iterator() { if (isEmpty()) { return Collections.emptyIterator(); + } else { + return new LinkedRefEntryIterator<>(entries()); } - return new LinkedRefEntryIterator<>(entries()); } @Nullable @@ -95,11 +96,18 @@ public void forEach(BiConsumer consumer) { @Override public Array keys(Class type) { - return Array.copyOf(keys(ArrayFactory.mutableArray(type, size()))); + if (isEmpty()) { + return Array.empty(type); + } else { + return Array.copyOf(keys(ArrayFactory.mutableArray(type, size()))); + } } @Override public > C keys(C container) { + if (isEmpty()) { + return container; + } for (E entry : entries()) { while (entry != null) { container.add(entry.key()); @@ -111,6 +119,9 @@ public > C keys(C container) { @Override public MutableArray keys(MutableArray container) { + if (isEmpty()) { + return container; + } UnsafeMutableArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); @@ -127,11 +138,18 @@ public MutableArray keys(MutableArray container) { @Override public Array values(Class type) { - return Array.copyOf(values(ArrayFactory.mutableArray(type, size()))); + if (isEmpty()) { + return Array.empty(type); + } else { + return Array.copyOf(values(ArrayFactory.mutableArray(type, size()))); + } } @Override public > C values(C container) { + if (isEmpty()) { + return container; + } for (E entry : entries()) { while (entry != null) { V value = entry.value(); @@ -146,10 +164,11 @@ public > C values(C container) { @Override public MutableArray values(MutableArray container) { - + if (isEmpty()) { + return container; + } UnsafeMutableArray unsafe = container.asUnsafe(); unsafe.prepareForSize(container.size() + size()); - for (E entry : entries()) { while (entry != null) { V value = entry.value(); @@ -159,7 +178,6 @@ public MutableArray values(MutableArray container) { entry = entry.next(); } } - return container; }