diff --git a/.github/release-settings.xml b/.github/release-settings.xml
new file mode 100644
index 0000000..be56a53
--- /dev/null
+++ b/.github/release-settings.xml
@@ -0,0 +1,20 @@
+
+
+
+ eu.maveniverse.maven.plugins
+
+
+
+
+ sonatype-central-portal
+ ${env.MAVEN_USER}
+ ${env.MAVEN_PASSWORD}
+
+ sonatype-cp
+ njord:template:release-sca
+
+
+
+
+
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..d6ad167
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,90 @@
+name: Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ releaseVersion:
+ description: "Release version, e.g. 0.3.6 (optional — auto-detected from the current POM)"
+ required: false
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write # to automatically create tags
+
+ steps:
+ - name: Validate release version
+ if: ${{ github.event.inputs.releaseVersion != '' }}
+ run: |
+ RELEASE=${{ github.event.inputs.releaseVersion }}
+ if [[ ! $RELEASE =~ ^[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$ ]]; then
+ echo "Error: releaseVersion '$RELEASE' is not in the correct format x.y.z or x.y.z-SNAPSHOT"
+ exit 1
+ fi
+
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: master
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ java-version: '21'
+ distribution: 'adopt'
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ gpg-passphrase: MAVEN_GPG_PASSPHRASE
+
+ - name: Configure git
+ run: |
+ git config user.email "actions@github.com"
+ git config user.name "GitHub Actions"
+
+ - name: Prepare Release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+ run: |
+ MVN_ARGS=""
+ if [ -n "${{ github.event.inputs.releaseVersion }}" ]; then
+ MVN_ARGS="$MVN_ARGS -DreleaseVersion=${{ github.event.inputs.releaseVersion }}"
+ fi
+ mvn -B release:prepare $MVN_ARGS
+
+ - name: Check release.properties
+ run: |
+ if [ ! -f release.properties ]; then
+ echo "release.properties not found"
+ exit 1
+ fi
+ echo "Contents of release.properties:"
+ cat release.properties
+
+ - name: Determine release version
+ id: version
+ run: |
+ export TAG=$(grep 'scm.tag=' release.properties | cut -d'=' -f2)
+ export VERSION=${TAG#JavaFastPFOR-}
+
+ echo "released_tag=${TAG}" >> $GITHUB_OUTPUT
+ echo "released_version=${VERSION}" >> $GITHUB_OUTPUT
+
+ echo "Releasing tag: ${TAG}"
+ echo "Releasing version: ${VERSION}"
+
+ - name: Release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+ MAVEN_GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ MAVEN_USER: ${{ secrets.MAVEN_USER }}
+ MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
+ run: |
+ mvn -B release:perform -Darguments="-DskipTests -DaltDeploymentRepository=id::default::njord: -Dnjord.autoPublish=true -Dnjord.publishingType=automatic" -s .github/release-settings.xml
+
+ - name: Create GitHub Release
+ run: gh release create "${{ steps.version.outputs.released_tag }}" --generate-notes --title "Version ${{ steps.version.outputs.released_version }}"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 88b3320..5a78c84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@ tags
target/
tmp/
/bin
+.idea
+*.iml
diff --git a/README.md b/README.md
index 2ef8fc5..0246789 100644
--- a/README.md
+++ b/README.md
@@ -104,53 +104,62 @@ non-vectorized implementation. For an example usage, see
examples/vector/Example.java. The feature requires JDK 19+ and is currently for
advanced users.
-JavaFastPFOR as a dependency (JitPack)
+JavaFastPFOR as a dependency
------------------------
+JavaFastPFOR is available both on Maven Central and JitPack, so you can easily
+include it in your project using either source.
+
We have a demo project using JavaFastPFOR as a dependency (both Maven and Gradle). See...
https://github.com/fast-pack/JavaFastPFORDemo
-1. **Maven**
+### Maven Central
+
+You can add JavaFastPFOR directly from Maven Central — no extra repository configuration needed:
-Using this code in your own project is easy with maven, just add
-the following code in your pom.xml file:
+**Maven**
```xml
-
- com.github.fast-pack
- JavaFastPFor
- JavaFastPFOR-0.3.2
-
+
+ me.lemire.integercompression
+ JavaFastPFOR
+ 0.3.8
+
```
-as well as jitpack as a repository...
+**Gradle (Groovy)**
-```xml
-
-
- jitpack.io
- https://jitpack.io
-
-
+```groovy
+dependencies {
+ implementation 'me.lemire.integercompression:JavaFastPFOR:0.3.8'
+}
```
-Naturally, you should replace "version" by the version
-you desire.
-
+### JitPack
-2. **Gradle (groovy)**
+If you prefer or need to use JitPack, you can include the dependency like this:
+**Maven**
-Then all you need is to edit your `build.gradle` file like so:
+```xml
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+
+ com.github.fast-pack
+ JavaFastPFOR
+ JavaFastPFOR-0.3.8
+
+```
+**Gradle (groovy)**
```groovy
-plugins {
- id 'java'
-}
-
-
repositories {
mavenCentral()
maven {
@@ -159,11 +168,10 @@ repositories {
}
dependencies {
- implementation 'com.github.fast-pack:JavaFastPFor:JavaFastPFOR-0.3.2'
+ implementation 'com.github.fast-pack:JavaFastPFOR:JavaFastPFOR-0.3.8'
}
```
-
Naturally, you should replace "version" by the version
you desire.
@@ -244,6 +252,21 @@ API Documentation
http://www.javadoc.io/doc/me.lemire.integercompression/JavaFastPFOR/
+
+Citing this work
+-----------------
+
+If you use JavaFastPFOR in your work, please consider citing the project. A recommended BibTeX entry is:
+
+```bibtex
+@misc{lemire2025_javafastpfor,
+ author = {Daniel Lemire},
+ title = {{JavaFastPFOR: A simple integer compression library in Java}},
+ year = {2025},
+ howpublished = {\url{https://github.com/fast-pack/JavaFastPFOR}},
+}
+```
+
Want to read more?
------------------
diff --git a/pom.xml b/pom.xml
index 928a3d2..33db8e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,13 +2,14 @@
4.0.0
me.lemire.integercompression
JavaFastPFOR
- 0.3.3-SNAPSHOT
+ 0.3.11-SNAPSHOT
jar
21
21
21
UTF-8
+ 0.8.5
@@ -19,11 +20,25 @@
- scm:git:git@github.com:fast-pack/JavaFastPFOR.git
- scm:git:git@github.com:fast-pack/JavaFastPFOR.git
- scm:git:git@github.com:fast-pack/JavaFastPFOR.git
+ scm:git:https://github.com/fast-pack/JavaFastPFOR.git
+ scm:git:https://github.com/fast-pack/JavaFastPFOR.git
+ scm:git:https://github.com/fast-pack/JavaFastPFOR.git
HEAD
+
+
+
+ sonatype-central-portal
+ Sonatype Central Portal
+ https://central.sonatype.com/repository/maven-snapshots/
+
+
+ sonatype-central-portal
+ Sonatype Central Portal
+ https://repo.maven.apache.org/maven2/
+
+
+
lemire
@@ -60,6 +75,13 @@
https://github.com/fast-pack/JavaFastPFOR/issues
+
+
+ eu.maveniverse.maven.njord
+ extension3
+ ${njord.version}
+
+
org.apache.maven.plugins
@@ -170,14 +192,33 @@
maven-release-plugin
3.0.1
- install
+ deploy
true
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.8
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ eu.maveniverse.maven.plugins
+ njord
+ ${njord.version}
+
maven-clean-plugin
2.5
diff --git a/src/main/java/me/lemire/integercompression/SkippableComposition.java b/src/main/java/me/lemire/integercompression/SkippableComposition.java
index 7dd4736..fc3c18e 100644
--- a/src/main/java/me/lemire/integercompression/SkippableComposition.java
+++ b/src/main/java/me/lemire/integercompression/SkippableComposition.java
@@ -52,12 +52,14 @@ public void headlessCompress(int[] in, IntWrapper inpos, int inlength, int[] out
public void headlessUncompress(int[] in, IntWrapper inpos, int inlength, int[] out,
IntWrapper outpos, int num) {
int init = inpos.get();
+ int outposInit = outpos.get();
+
F1.headlessUncompress(in, inpos, inlength, out, outpos, num);
if (inpos.get() == init) {
inpos.increment();
}
inlength -= inpos.get() - init;
- num -= outpos.get();
+ num -= outpos.get() - outposInit;
F2.headlessUncompress(in, inpos, inlength, out, outpos, num);
}
diff --git a/src/main/java/me/lemire/integercompression/differential/SkippableIntegratedComposition.java b/src/main/java/me/lemire/integercompression/differential/SkippableIntegratedComposition.java
index a1379ad..4786ec5 100644
--- a/src/main/java/me/lemire/integercompression/differential/SkippableIntegratedComposition.java
+++ b/src/main/java/me/lemire/integercompression/differential/SkippableIntegratedComposition.java
@@ -66,13 +66,15 @@ public void headlessUncompress(int[] in, IntWrapper inpos, int inlength,
if (inlength == 0)
return;
int init = inpos.get();
+ int outposInit = outpos.get();
+
F1.headlessUncompress(in, inpos, inlength, out, outpos,num,initvalue);
if (inpos.get() == init) {
inpos.increment();
}
inlength -= inpos.get() - init;
- num -= outpos.get();
+ num -= outpos.get() - outposInit;
F2.headlessUncompress(in, inpos, inlength, out, outpos,num,initvalue);
}
diff --git a/src/main/java/me/lemire/integercompression/synth/UniformDataGenerator.java b/src/main/java/me/lemire/integercompression/synth/UniformDataGenerator.java
index bbd386a..a50497c 100644
--- a/src/main/java/me/lemire/integercompression/synth/UniformDataGenerator.java
+++ b/src/main/java/me/lemire/integercompression/synth/UniformDataGenerator.java
@@ -42,7 +42,7 @@ int[] generateUniformHash(int N, int Max) {
int[] ans = new int[N];
HashSet s = new HashSet();
while (s.size() < N)
- s.add(new Integer(this.rand.nextInt(Max)));
+ s.add(this.rand.nextInt(Max));
Iterator i = s.iterator();
for (int k = 0; k < N; ++k)
ans[k] = i.next().intValue();
diff --git a/src/main/java/me/lemire/longcompression/SkippableLongComposition.java b/src/main/java/me/lemire/longcompression/SkippableLongComposition.java
index 0f9800e..eb03b72 100644
--- a/src/main/java/me/lemire/longcompression/SkippableLongComposition.java
+++ b/src/main/java/me/lemire/longcompression/SkippableLongComposition.java
@@ -53,12 +53,14 @@ public void headlessCompress(long[] in, IntWrapper inpos, int inlength, long[] o
public void headlessUncompress(long[] in, IntWrapper inpos, int inlength, long[] out,
IntWrapper outpos, int num) {
int init = inpos.get();
+ int outposInit = outpos.get();
+
F1.headlessUncompress(in, inpos, inlength, out, outpos, num);
if (inpos.get() == init) {
inpos.increment();
}
inlength -= inpos.get() - init;
- num -= outpos.get();
+ num -= outpos.get() - outposInit;
F2.headlessUncompress(in, inpos, inlength, out, outpos, num);
}
diff --git a/src/main/java/me/lemire/longcompression/differential/LongDelta.java b/src/main/java/me/lemire/longcompression/differential/LongDelta.java
index 184e53c..8399f94 100644
--- a/src/main/java/me/lemire/longcompression/differential/LongDelta.java
+++ b/src/main/java/me/lemire/longcompression/differential/LongDelta.java
@@ -107,7 +107,7 @@ public static void fastinverseDelta(long[] data) {
}
}
- for (; i != data.length; ++i) {
+ for (; i < data.length; ++i) {
data[i] += data[i - 1];
}
}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index d254c12..f134601 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -6,5 +6,7 @@
// requires jdk.incubator.vector;
exports me.lemire.integercompression;
exports me.lemire.longcompression;
+ exports me.lemire.longcompression.differential;
+ exports me.lemire.integercompression.differential;
// exports me.lemire.integercompression.vector;
}
diff --git a/src/test/java/me/lemire/integercompression/SkippableBasicTest.java b/src/test/java/me/lemire/integercompression/SkippableBasicTest.java
index 57a07e3..881dada 100644
--- a/src/test/java/me/lemire/integercompression/SkippableBasicTest.java
+++ b/src/test/java/me/lemire/integercompression/SkippableBasicTest.java
@@ -15,6 +15,7 @@
import me.lemire.integercompression.differential.SkippableIntegratedIntegerCODEC;
import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -236,4 +237,52 @@ private static void testMaxHeadlessCompressedLength(SkippableIntegerCODEC codec,
}
}
}
+
+ @Test
+ public void testUncompressOutputOffset_SkippableComposition() {
+ for (int offset : new int[] {0, 1, 6}) {
+ SkippableComposition codec = new SkippableComposition(new BinaryPacking(), new VariableByte());
+
+ int[] input = { 2, 3, 4, 5 };
+ int[] compressed = new int[codec.maxHeadlessCompressedLength(new IntWrapper(0), input.length)];
+ int[] uncompressed = new int[offset + input.length];
+
+ IntWrapper inputOffset = new IntWrapper(0);
+ IntWrapper compressedOffset = new IntWrapper(0);
+
+ codec.headlessCompress(input, inputOffset, input.length, compressed, compressedOffset);
+
+ int compressedLength = compressedOffset.get();
+ IntWrapper uncompressedOffset = new IntWrapper(offset);
+ compressedOffset = new IntWrapper(0);
+ codec.headlessUncompress(compressed, compressedOffset, compressedLength, uncompressed, uncompressedOffset, input.length);
+
+ assertArrayEquals(input, Arrays.copyOfRange(uncompressed, offset, offset + input.length));
+ }
+ }
+
+ @Test
+ public void testUncompressOutputOffset_SkippableIntegratedComposition() {
+ for (int offset : new int[] {0, 1, 6}) {
+ SkippableIntegratedComposition codec = new SkippableIntegratedComposition(new IntegratedBinaryPacking(), new IntegratedVariableByte());
+
+ int[] input = { 2, 3, 4, 5 };
+ int[] compressed = new int[codec.maxHeadlessCompressedLength(new IntWrapper(0), input.length)];
+ int[] uncompressed = new int[offset + input.length];
+
+ IntWrapper inputOffset = new IntWrapper(0);
+ IntWrapper compressedOffset = new IntWrapper(0);
+ IntWrapper initValue = new IntWrapper(0);
+
+ codec.headlessCompress(input, inputOffset, input.length, compressed, compressedOffset, initValue);
+
+ int compressedLength = compressedOffset.get();
+ IntWrapper uncompressedOffset = new IntWrapper(offset);
+ compressedOffset = new IntWrapper(0);
+ initValue = new IntWrapper(0);
+ codec.headlessUncompress(compressed, compressedOffset, compressedLength, uncompressed, uncompressedOffset, input.length, initValue);
+
+ assertArrayEquals(input, Arrays.copyOfRange(uncompressed, offset, offset + input.length));
+ }
+ }
}
diff --git a/src/test/java/me/lemire/longcompression/LongDeltaTest.java b/src/test/java/me/lemire/longcompression/LongDeltaTest.java
new file mode 100644
index 0000000..bfa1e6f
--- /dev/null
+++ b/src/test/java/me/lemire/longcompression/LongDeltaTest.java
@@ -0,0 +1,23 @@
+package me.lemire.longcompression;
+
+import me.lemire.longcompression.differential.LongDelta;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class LongDeltaTest {
+ @Test
+ public void testEmptyArrayFastInverseDelta() {
+ LongCompressor compressor = new LongCompressor();
+ long[] input = new long[0];
+
+ LongDelta.delta(input);
+ long[] compressed = compressor.compress(input);
+ long[] result = compressor.uncompress(compressed);
+ LongDelta.fastinverseDelta(result);
+
+ assertNotNull(result);
+ assertArrayEquals(input, result);
+ }
+}
diff --git a/src/test/java/me/lemire/longcompression/SkippableLongBasicTest.java b/src/test/java/me/lemire/longcompression/SkippableLongBasicTest.java
index 4309e9d..c4b7e01 100644
--- a/src/test/java/me/lemire/longcompression/SkippableLongBasicTest.java
+++ b/src/test/java/me/lemire/longcompression/SkippableLongBasicTest.java
@@ -15,6 +15,7 @@
import me.lemire.integercompression.TestUtils;
import me.lemire.integercompression.VariableByte;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -167,4 +168,27 @@ private static void testMaxHeadlessCompressedLength(SkippableLongCODEC codec, in
assertTrue(maxOutputLength <= outPos.get() + 1); // +1 because SkippableLongComposition always adds one extra integer for the potential header
}
}
+
+ @Test
+ public void testUncompressOutputOffset_SkippableLongComposition() {
+ for (int offset : new int[] {0, 1, 6}) {
+ SkippableLongComposition codec = new SkippableLongComposition(new LongBinaryPacking(), new LongVariableByte());
+
+ long[] input = { 2, 3, 4, 5 };
+ long[] compressed = new long[codec.maxHeadlessCompressedLength(new IntWrapper(0), input.length)];
+ long[] uncompressed = new long[offset + input.length];
+
+ IntWrapper inputOffset = new IntWrapper(0);
+ IntWrapper compressedOffset = new IntWrapper(0);
+
+ codec.headlessCompress(input, inputOffset, input.length, compressed, compressedOffset);
+
+ int compressedLength = compressedOffset.get();
+ IntWrapper uncompressedOffset = new IntWrapper(offset);
+ compressedOffset = new IntWrapper(0);
+ codec.headlessUncompress(compressed, compressedOffset, compressedLength, uncompressed, uncompressedOffset, input.length);
+
+ assertArrayEquals(input, Arrays.copyOfRange(uncompressed, offset, offset + input.length));
+ }
+ }
}