diff --git a/.github/dependabot.yml b/.github/dependabot.yml index daec318..2390d8c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,10 @@ version: 2 updates: - - package-ecosystem: "maven" + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "monthly" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d451d13..abc6637 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,7 @@ on: branches: - master - "4.3" + - "3.0" paths-ignore: - "README.md" - "release-notes/*" @@ -14,31 +15,33 @@ permissions: contents: read jobs: build: - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-latest' strategy: fail-fast: false matrix: - # Alas, JDK14 can't be yet used as JUG builds for Java 6 - java_version: ['8', '11'] + java_version: ['8', '11', '17', '21'] env: JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: "temurin" java-version: ${{ matrix.java_version }} cache: 'maven' + server-id: central-snapshots + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD - name: Build run: ./mvnw -B -q -ff -ntp verify - name: Generate code coverage - if: github.event_name != 'pull_request' && matrix.java_version == '8' + if: ${{ github.event_name != 'pull_request' && matrix.java_version == '8' }} run: ./mvnw -B -q -ff -ntp test - name: Publish code coverage - if: github.event_name != 'pull_request' && matrix.java_version == '8' - uses: codecov/codecov-action@v3 + if: ${{ github.event_name != 'pull_request' && matrix.java_version == '8' }} + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} - file: ./target/site/jacoco/jacoco.xml + files: ./target/site/jacoco/jacoco.xml flags: unittests diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 5366408..b9b1153 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,5 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar diff --git a/README.md b/README.md index 83e1eed..26bd017 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,36 @@ JUG is a set of Java classes for working with UUIDs: generating UUIDs using any of standard methods, outputting efficiently, sorting and so on. -It generates UUIDs according to the [UUID specification (RFC-4122)](https://tools.ietf.org/html/rfc4122) -(also see [Wikipedia UUID page](http://en.wikipedia.org/wiki/UUID) for more explanation) +It generates UUIDs according to the [UUID specification (RFC-9562)](https://tools.ietf.org/html/rfc9562) +(see [Wikipedia UUID page](http://en.wikipedia.org/wiki/UUID) for more explanation) JUG was written by Tatu Saloranta () originally in 2002 and has been updated over the years. In addition, many other individuals have helped fix bugs and implement new features: please see `release-notes/CREDITS` for the complete list. JUG is licensed under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). +## Supported UUID versions (1, 3, 4, 5, 6, 7) + +JUG supports both "classic" versions defined in [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122): + +* `1`: time/location - based +* `3` and `5`: name hash - based +* `4`: random number - based + +and newly (in 2022-) proposed (see [uuid6](https://uuid6.github.io/uuid6-ietf-draft/) and [RFC-9562](https://datatracker.ietf.org/doc/html/rfc9562) variants: + +* `6`: reordered variant of version `1` (with lexicographic ordering) +* `7`: Unix-timestamp + random based variant (also with lexicographic ordering) + ## Status | Type | Status | | ---- | ------ | | Build (CI) | [![Build (github)](https://github.com/cowtowncoder/java-uuid-generator/actions/workflows/main.yml/badge.svg)](https://github.com/cowtowncoder/java-uuid-generator/actions/workflows/main.yml) | -| Artifact | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.uuid/java-uuid-generator/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.uuid/java-uuid-generator/) | +| Artifact | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.uuid/java-uuid-generator/badge.svg)](https://search.maven.org/artifact/com.fasterxml.uuid/java-uuid-generator) | | OSS Sponsorship | [![Tidelift](https://tidelift.com/badges/package/maven/com.fasterxml.uuid:java-uuid-generator)](https://tidelift.com/subscription/pkg/maven-com-fasterxml-uuid-java-uuid-generator?utm_source=maven-com-fasterxml-uuid-java-uuid-generator&utm_medium=referral&utm_campaign=readme) | | Javadocs | [![Javadoc](https://javadoc.io/badge/com.fasterxml.uuid/java-uuid-generator.svg)](http://www.javadoc.io/doc/com.fasterxml.uuid/java-uuid-generator) -| Code coverage (4.x) | [![codecov.io](https://codecov.io/github/cowtowncoder/java-uuid-generator/coverage.svg?branch=master)](https://codecov.io/github/cowtowncoder/java-uuid-generator?branch=master) | +| Code coverage (5.x) | [![codecov.io](https://codecov.io/github/cowtowncoder/java-uuid-generator/coverage.svg?branch=master)](https://codecov.io/github/cowtowncoder/java-uuid-generator?branch=master) | | OpenSSF Score | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/cowtowncoder/java-uuid-generator/badge)](https://securityscorecards.dev/viewer/?uri=github.com/cowtowncoder/java-uuid-generator) | ## Usage @@ -34,10 +47,17 @@ Maven coordinates are: com.fasterxml.uuid java-uuid-generator - 4.1.1 + 5.1.0 ``` + +Gradle: + +```groovy +implementation 'com.fasterxml.uuid:java-uuid-generator:5.1.0' +``` + #### Third-party Dependencies by JUG The only dependency for JUG is the logging library: @@ -67,6 +87,8 @@ UUID uuid = Generators.nameBasedgenerator().generate("string to hash"); // Versi // With JUG 4.1+: support for https://github.com/uuid6/uuid6-ietf-draft versions 6 and 7: UUID uuid = Generators.timeBasedReorderedGenerator().generate(); // Version 6 UUID uuid = Generators.timeBasedEpochGenerator().generate(); // Version 7 +// With JUG 5.0 added variation: +UUID uuid = Generators.timeBasedEpochRandomGenerator().generate(); // Version 7 with per-call random values ``` If you want customize generators, you may also just want to hold on to generator instance: @@ -135,31 +157,33 @@ it is rather slower than JUG version: for more information, read JUG jar built under `target/`: ``` -target/java-uuid-generator-4.1.2-SNAPSHOT.jar +target/java-uuid-generator-5.1.0-SNAPSHOT.jar ``` can also be used as a simple Command-line UUID generation tool. To see usage you can do something like: - java -jar target/java-uuid-generator-4.1.2-SNAPSHOT.jar + java -jar target/java-uuid-generator-5.1.0-SNAPSHOT.jar and get full instructions, but to generate 5 Random-based UUIDs, you would use: - java -jar target/java-uuid-generator-4.1.2-SNAPSHOT.jar -c 5 r + java -jar target/java-uuid-generator-5.1.0-SNAPSHOT.jar -c 5 r (where `-c` (or `--count`) means number of UUIDs to generate, and `r` means Random-based version) NOTE: this functionality is included as of JUG 4.1 -- with earlier versions you would need a bit longer invocation as Jar metadata did not specify "Main-Class". If so, you would need to use - java -cp target/java-uuid-generator-4.1.2-SNAPSHOT.jar com.fasterxml.uuid.Jug -c 5 r + java -cp target/java-uuid-generator-5.1.0-SNAPSHOT.jar com.fasterxml.uuid.Jug -c 5 r ## Compatibility JUG versions 3.1 and later require JDK 1.6 to work, mostly to be able to access local Ethernet MAC address. Earlier versions (3.0 and before) worked on 1.4 (which introduced `java.util.UUID`). +JUG versions 5.0 and later require JDK 8 to work. + ## Known Issues JDK's `java.util.UUID` has flawed implementation of `compareTo()`, which uses naive comparison diff --git a/pom.xml b/pom.xml index d00b3dc..7d2af65 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.fasterxml oss-parent - 54 + 68 com.fasterxml.uuid java-uuid-generator @@ -15,20 +15,20 @@ --> bundle Java UUID Generator - 4.3.1-SNAPSHOT + 5.1.1-SNAPSHOT Java UUID Generator (JUG) is a Java library for generating Universally Unique IDentifiers, UUIDs (see http://en.wikipedia.org/wiki/UUID). It can be used either as a component in a bigger application, or as a standalone command line tool. JUG generates UUIDs according to the IETF UUID draft specification. -JUG supports all 3 official UUID generation methods. +JUG supports 3 original official UUID generation methods as well as later additions (v6, v7) https://github.com/cowtowncoder/java-uuid-generator scm:git:git://github.com/cowtowncoder/java-uuid-generator.git https://github.com/cowtowncoder/java-uuid-generator scm:git:git@github.com:cowtowncoder/java-uuid-generator.git - java-uuid-generator-4.3.0 + java-uuid-generator-5.1.0 @@ -45,7 +45,7 @@ JUG supports all 3 official UUID generation methods. UTF-8 1.7.36 - 2023-09-13T00:45:46Z + 2024-06-02T23:59:30Z @@ -91,22 +91,10 @@ JUG supports all 3 official UUID generation methods. maven-compiler-plugin ${version.plugin.compiler} - 1.6 - 1.6 + 1.8 + 1.8 - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - @@ -144,12 +132,14 @@ https://stackoverflow.com/questions/37958104/maven-javadoc-no-source-files-for-p com.fasterxml.uuid;version="${project.version}", com.fasterxml.uuid.ext;version="${project.version}", - com.fasterxml.uuid.impl;version="${project.version}" + com.fasterxml.uuid.impl;version="${project.version}", + com.fasterxml.uuid.jug;version="${project.version}" com.fasterxml.uuid;version="[${project.version},${project.version}]", com.fasterxml.uuid.ext;version="[${project.version},${project.version}]", com.fasterxml.uuid.impl;version="[${project.version},${project.version}]", + com.fasterxml.uuid.jug;version="[${project.version},${project.version}]", org.slf4j;version="[${slf4j.version},2)" @@ -209,6 +199,11 @@ https://stackoverflow.com/questions/37958104/maven-javadoc-no-source-files-for-p + + + org.sonatype.central + central-publishing-maven-plugin + diff --git a/release-notes/CREDITS b/release-notes/CREDITS index ecaf1c1..a4819df 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -131,3 +131,23 @@ Paul Galbraith (pgalbraith@github) * Contributed #73: Add `Generators.defaultTimeBasedGenerator()` to use "default" interface address for time/based UUIDs [4.2.0] + +Pavel Raev (magdel@github) + * Contributed #81: Add UUIDUtil.extractTimestamp() for extracting 64-bit + timestamp for all timestamp-based versions + [5.0.0] + * Contributed #94 Add alternate version to UUIDv7 generator that uses random + values on every call (not just for different timestamp) + [5.0.0] + +Maia Everett (Maia-Everett@github) + * Contributed #85: Fix `LazyRandom` for native code generation tools + [5.0.0] + +Daniel Albuquerque (worldtiki@github) + * Contributed #99: New factory method to create TimeBasedEpochRandomGenerator + [5.1.0] + +Alexander Ilinykh (divinenickname@github) + * Contributed improvements to README.md, pom.xml (OSGi inclusion) + [5.1.1] diff --git a/release-notes/VERSION b/release-notes/VERSION index ddaf53f..14a5670 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -4,6 +4,33 @@ Project: java-uuid-generator Releases ============================================================================ +(not yet released) + +#122: RFC-4122 Obsoleted by RFC-9562 (document change) + (pointed out by @akefirad) +- Update to `oss-parent` v68 to switch to Central Portal publishing +- Branch "master" renamed as "main" + +5.1.0 (02-Jun-2024) + +#99: New factory method to create TimeBasedEpochRandomGenerator + (contributed by Daniel A) +#105: `UUIDUtil.extractTimestamp()` is broken for versions 1 and 6 + (contributed by @magdel) + +5.0.0 (23-Feb-2024) + +#53: Increase JDK baseline to JDK 8 +#81: Add `UUIDUtil.extractTimestamp()` for extracting 64-bit timestamp for + all timestamp-based versions + (requested by @gabrielbalan) + (contributed by @magdel) +#85: Fix `LazyRandom` for native code generation tools + (contributed by @Maia-Everett) +#94: Add alternate version to UUIDv7 generator that uses random values on every + call (not just for different timestamp) + (contributed by @magdel) + 4.3.0 (12-Sep-2023) #78: TimeBasedEpochGenerator (UUIDv7) can't be provided a `UUIDClock` diff --git a/src/main/java/com/fasterxml/uuid/EthernetAddress.java b/src/main/java/com/fasterxml/uuid/EthernetAddress.java index 8e61c17..f3222d5 100644 --- a/src/main/java/com/fasterxml/uuid/EthernetAddress.java +++ b/src/main/java/com/fasterxml/uuid/EthernetAddress.java @@ -33,7 +33,7 @@ public class EthernetAddress { private static final long serialVersionUID = 1L; - private final static char[] HEX_CHARS = "0123456789abcdefABCDEF".toCharArray(); + private static final char[] HEX_CHARS = "0123456789abcdefABCDEF".toCharArray(); /** * We may need a random number generator, for creating dummy ethernet diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index 1c4fe6b..9ab587a 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -23,6 +23,7 @@ import com.fasterxml.uuid.impl.NameBasedGenerator; import com.fasterxml.uuid.impl.RandomBasedGenerator; import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; +import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator; import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator; import com.fasterxml.uuid.impl.TimeBasedGenerator; @@ -52,7 +53,7 @@ public class Generators /** * Factory method for constructing UUID generator that uses default (shared) * random number generator for constructing UUIDs according to standard - * method number 4. + * version 4. */ public static RandomBasedGenerator randomBasedGenerator() { return randomBasedGenerator(null); @@ -61,7 +62,7 @@ public static RandomBasedGenerator randomBasedGenerator() { /** * Factory method for constructing UUID generator that uses specified * random number generator for constructing UUIDs according to standard - * method number 4. + * version 4. */ public static RandomBasedGenerator randomBasedGenerator(Random rnd) { return new RandomBasedGenerator(rnd); @@ -72,8 +73,8 @@ public static RandomBasedGenerator randomBasedGenerator(Random rnd) { /** * Factory method for constructing UUID generator that uses specified * random number generator for constructing UUIDs according to standard - * method number 5, but without using a namespace. - * Digester to use will be SHA-1 as recommened by UUID spec. + * version 5, but without using a namespace. + * Digester to use will be SHA-1 as recommended by UUID spec. */ public static NameBasedGenerator nameBasedGenerator() { return nameBasedGenerator(null); @@ -82,7 +83,7 @@ public static NameBasedGenerator nameBasedGenerator() { /** * Factory method for constructing UUID generator that uses specified * random number generator for constructing UUIDs according to standard - * method number 5, with specified namespace (or without one if null + * version 5, with specified namespace (or without one if null * is specified). * Digester to use will be SHA-1 as recommened by UUID spec. * @@ -97,7 +98,7 @@ public static NameBasedGenerator nameBasedGenerator(UUID namespace) { /** * Factory method for constructing UUID generator that uses specified * random number generator for constructing UUIDs according to standard - * method number 3 or 5, with specified namespace (or without one if null + * version 3 or 5, with specified namespace (or without one if null * is specified), using specified digester. * If digester is passed as null, a SHA-1 digester will be constructed. * @@ -125,6 +126,11 @@ public static NameBasedGenerator nameBasedGenerator(UUID namespace, MessageDiges /** * Factory method for constructing UUID generator that generates UUID using * version 7 (Unix Epoch time+random based). + *

+ * NOTE: calls within same millisecond produce very similar values; this may be + * unsafe in some environments. + *

+ * No additional external synchronization is used. */ public static TimeBasedEpochGenerator timeBasedEpochGenerator() { @@ -135,6 +141,10 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator() * Factory method for constructing UUID generator that generates UUID using * version 7 (Unix Epoch time+random based), using specified {@link Random} * number generator. + *

+ * NOTE: calls within same millisecond produce very similar values; this may be + * unsafe in some environments. + *

* No additional external synchronization is used. */ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random) @@ -145,9 +155,12 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random) /** * Factory method for constructing UUID generator that generates UUID using * version 7 (Unix Epoch time+random based), using specified {@link Random} - * number generato. - * Timestamp to use is accessed using specified {@link UUIDClock} - * + * number generator. + * Timestamp to use is accessed using specified {@link UUIDClock}. + *

+ * NOTE: calls within same millisecond produce very similar values; this may be + * unsafe in some environments. + *

* No additional external synchronization is used. * * @since 4.3 @@ -158,6 +171,60 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random, return new TimeBasedEpochGenerator(random, clock); } + // // Epoch Time+random generation + + /** + * Factory method for constructing UUID generator that generates UUID using + * version 7 (Unix Epoch time+random based). + *

+ * Calls within same millisecond use additional per-call randomness to try to create + * more distinct values, compared to {@link #timeBasedEpochGenerator(Random)} + *

+ * No additional external synchronization is used. + * + * @since 5.1 + */ + public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator() + { + return timeBasedEpochRandomGenerator(null); + } + + /** + * Factory method for constructing UUID generator that generates UUID using + * version 7 (Unix Epoch time+random based), using specified {@link Random} + * number generator. + *

+ * Calls within same millisecond use additional per-call randomness to try to create + * more distinct values, compared to {@link #timeBasedEpochGenerator(Random)} + *

+ * No additional external synchronization is used. + * + * @since 5.0 + */ + public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator(Random random) + { + return new TimeBasedEpochRandomGenerator(random); + } + + /** + * Factory method for constructing UUID generator that generates UUID using + * version 7 (Unix Epoch time+random based), using specified {@link Random} + * number generator. + * Timestamp to use is accessed using specified {@link UUIDClock} + *

+ * Calls within same millisecond use additional per-call randomness to try to create + * more distinct values, compared to {@link #timeBasedEpochGenerator(Random)} + *

+ * No additional external synchronization is used. + * + * @since 5.0 + */ + public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator(Random random, + UUIDClock clock) + { + return new TimeBasedEpochRandomGenerator(random, clock); + } + // // Time+location-based generation /** diff --git a/src/main/java/com/fasterxml/uuid/Jug.java b/src/main/java/com/fasterxml/uuid/Jug.java index 08aac47..b019447 100644 --- a/src/main/java/com/fasterxml/uuid/Jug.java +++ b/src/main/java/com/fasterxml/uuid/Jug.java @@ -33,6 +33,7 @@ public class Jug TYPES.put("name-based", "n"); TYPES.put("reordered-time-based", "o"); // Version 6 TYPES.put("epoch-time-based", "e"); // Version 7 + TYPES.put("random-epoch-time-based", "m"); // Version 7 but more random } protected final static HashMap OPTIONS = new HashMap(); @@ -46,7 +47,7 @@ public class Jug OPTIONS.put("verbose", "v"); } - protected static void printUsage() + protected void printUsage() { String clsName = Jug.class.getName(); System.err.println("Usage: java "+clsName+" [options] type"); @@ -74,7 +75,7 @@ protected static void printUsage() System.err.println(" epoch-based / e: generate UUID based on current time (as 'epoch') and random number"); } - private static void printMap(Map m, PrintStream out, boolean option) + private void printMap(Map m, PrintStream out, boolean option) { int i = 0; int len = m.size(); @@ -101,6 +102,10 @@ private static void printMap(Map m, PrintStream out, boolean opti public static void main(String[] args) { + new Jug().run(args); + } + + public void run(String[] args) { if (args.length == 0) { printUsage(); return; @@ -122,7 +127,7 @@ public static void main(String[] args) if (tmp == null) { if (!TYPES.containsValue(type)) { System.err.println("Unrecognized UUID generation type '"+ - type+"'; currently available ones are:"); + type+"'; currently available ones are:"); printMap(TYPES, System.err, false); System.err.println(); System.exit(1); @@ -135,7 +140,7 @@ public static void main(String[] args) NoArgGenerator noArgGenerator = null; // random- or time-based StringArgGenerator nameArgGenerator = null; // name-based - + for (int i = 0; i < count; ++i) { String opt = args[i]; @@ -169,46 +174,46 @@ public static void main(String[] args) try { String next; switch (option) { - case 'c': - // Need a number now: - next = args[++i]; - try { - genCount = Integer.parseInt(next); - } catch (NumberFormatException nex) { - System.err.println("Invalid number argument for option '"+opt+"', exiting."); - System.exit(1); - } - if (genCount < 1) { - System.err.println("Invalid number argument for option '"+opt+"'; negative numbers not allowed, ignoring (defaults to 1)."); - } - break; - case 'e': - // Need the ethernet address: - next = args[++i]; - try { - addr = EthernetAddress.valueOf(next); - } catch (NumberFormatException nex) { - System.err.println("Invalid ethernet address for option '"+opt+"', error: "+nex.toString()); - System.exit(1); - } - break; - case 'h': - printUsage(); - return; - case 'n': - // Need the name - name = args[++i]; - break; - case 'p': // performance: - performance = true; - break; - case 's': - // Need the namespace id - nameSpace = args[++i]; - break; - case 'v': - verbose = true; - break; + case 'c': + // Need a number now: + next = args[++i]; + try { + genCount = Integer.parseInt(next); + } catch (NumberFormatException nex) { + System.err.println("Invalid number argument for option '"+opt+"', exiting."); + System.exit(1); + } + if (genCount < 1) { + System.err.println("Invalid number argument for option '"+opt+"'; negative numbers not allowed, ignoring (defaults to 1)."); + } + break; + case 'e': + // Need the ethernet address: + next = args[++i]; + try { + addr = EthernetAddress.valueOf(next); + } catch (NumberFormatException nex) { + System.err.println("Invalid ethernet address for option '"+opt+"', error: "+nex.toString()); + System.exit(1); + } + break; + case 'h': + printUsage(); + return; + case 'n': + // Need the name + name = args[++i]; + break; + case 'p': // performance: + performance = true; + break; + case 's': + // Need the namespace id + nameSpace = args[++i]; + break; + case 'v': + verbose = true; + break; } } catch (IndexOutOfBoundsException ie) { // We get here when an arg is missing... @@ -226,70 +231,80 @@ public static void main(String[] args) boolean usesRnd = false; switch (typeC) { - case 't': // time-based - case 'o': // reordered-time-based (Version 6) - // 30-Jun-2022, tatu: Is this true? My former self must have had his - // reasons so leaving as is but... odd. - usesRnd = true; - // No address specified? Need a dummy one... - if (addr == null) { - if (verbose) { - System.out.print("(no address specified, generating dummy address: "); + case 't': // time-based + case 'o': // reordered-time-based (Version 6) + // 30-Jun-2022, tatu: Is this true? My former self must have had his + // reasons so leaving as is but... odd. + usesRnd = true; + // No address specified? Need a dummy one... + if (addr == null) { + if (verbose) { + System.out.print("(no address specified, generating dummy address: "); + } + addr = EthernetAddress.constructMulticastAddress(new java.util.Random(System.currentTimeMillis())); + if (verbose) { + System.out.print(addr.toString()); + System.out.println(")"); + } } - addr = EthernetAddress.constructMulticastAddress(new java.util.Random(System.currentTimeMillis())); - if (verbose) { - System.out.print(addr.toString()); - System.out.println(")"); + noArgGenerator = (typeC == 't') + ? Generators.timeBasedGenerator(addr) + : Generators.timeBasedReorderedGenerator(addr); + break; + case 'r': // random-based + usesRnd = true; + { + SecureRandom r = new SecureRandom(); + if (verbose) { + System.out.print("(using secure random generator, info = '"+r.getProvider().getInfo()+"')"); + } + noArgGenerator = Generators.randomBasedGenerator(r); } - } - noArgGenerator = (typeC == 't') - ? Generators.timeBasedGenerator(addr) - : Generators.timeBasedReorderedGenerator(addr); - break; - case 'r': // random-based - usesRnd = true; - { - SecureRandom r = new SecureRandom(); - if (verbose) { - System.out.print("(using secure random generator, info = '"+r.getProvider().getInfo()+"')"); + break; + case 'e': // epoch-time-based + usesRnd = true; + { + SecureRandom r = new SecureRandom(); + if (verbose) { + System.out.print("(using secure random generator, info = '"+r.getProvider().getInfo()+"')"); + } + noArgGenerator = Generators.timeBasedEpochGenerator(r); } - noArgGenerator = Generators.randomBasedGenerator(r); - } - break; - case 'e': // epoch-time-based - usesRnd = true; - { - SecureRandom r = new SecureRandom(); - if (verbose) { - System.out.print("(using secure random generator, info = '"+r.getProvider().getInfo()+"')"); + break; + case 'm': // random-epoch-time-based + usesRnd = true; + { + SecureRandom r = new SecureRandom(); + if (verbose) { + System.out.print("(using secure random generator, info = '"+r.getProvider().getInfo()+"')"); + } + noArgGenerator = Generators.timeBasedEpochRandomGenerator(r); } - noArgGenerator = Generators.timeBasedEpochGenerator(r); - } - break; - case 'n': // name-based - if (nameSpace == null) { - System.err.println("--name-space (-s) - argument missing when using method that requires it, exiting."); - System.exit(1); - } - if (name == null) { - System.err.println("--name (-n) - argument missing when using method that requires it, exiting."); - System.exit(1); - } - if (typeC == 'n') { - String orig = nameSpace; - nameSpace = nameSpace.toLowerCase(); - if (nameSpace.equals("url")) { - nsUUID = NameBasedGenerator.NAMESPACE_URL; - } else if (nameSpace.equals("dns")) { - nsUUID = NameBasedGenerator.NAMESPACE_DNS; - } else { - System.err.println("Unrecognized namespace '"+orig - +"'; only DNS and URL allowed for name-based generation."); + break; + case 'n': // name-based + if (nameSpace == null) { + System.err.println("--name-space (-s) - argument missing when using method that requires it, exiting."); System.exit(1); } - } - nameArgGenerator = Generators.nameBasedGenerator(nsUUID); - break; + if (name == null) { + System.err.println("--name (-n) - argument missing when using method that requires it, exiting."); + System.exit(1); + } + if (typeC == 'n') { + String orig = nameSpace; + nameSpace = nameSpace.toLowerCase(); + if (nameSpace.equals("url")) { + nsUUID = NameBasedGenerator.NAMESPACE_URL; + } else if (nameSpace.equals("dns")) { + nsUUID = NameBasedGenerator.NAMESPACE_DNS; + } else { + System.err.println("Unrecognized namespace '"+orig + +"'; only DNS and URL allowed for name-based generation."); + System.exit(1); + } + } + nameArgGenerator = Generators.nameBasedGenerator(nsUUID); + break; } // And then let's rock: diff --git a/src/main/java/com/fasterxml/uuid/UUIDClock.java b/src/main/java/com/fasterxml/uuid/UUIDClock.java index ea48b6d..28493c6 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDClock.java +++ b/src/main/java/com/fasterxml/uuid/UUIDClock.java @@ -26,12 +26,12 @@ */ public class UUIDClock { - private final static UUIDClock DEFAULT = new UUIDClock(); + private static final UUIDClock DEFAULT = new UUIDClock(); /** * @since 4.3 */ - public final static UUIDClock systemTimeClock() { + public static UUIDClock systemTimeClock() { return DEFAULT; } diff --git a/src/main/java/com/fasterxml/uuid/UUIDGenerator.java b/src/main/java/com/fasterxml/uuid/UUIDGenerator.java index aa81034..ebd1ec3 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDGenerator.java +++ b/src/main/java/com/fasterxml/uuid/UUIDGenerator.java @@ -46,4 +46,32 @@ protected UUIDGenerator() { } * generator instance will produce. */ public abstract UUIDType getType(); + + /* + /********************************************************** + /* Helper methods for implementations + /********************************************************** + */ + + protected final static long _toLong(byte[] buffer, int offset) + { + long l1 = _toInt(buffer, offset); + long l2 = _toInt(buffer, offset+4); + long l = (l1 << 32) + ((l2 << 32) >>> 32); + return l; + } + + protected final static long _toInt(byte[] buffer, int offset) + { + return (buffer[offset] << 24) + + ((buffer[++offset] & 0xFF) << 16) + + ((buffer[++offset] & 0xFF) << 8) + + (buffer[++offset] & 0xFF); + } + + protected final static long _toShort(byte[] buffer, int offset) + { + return ((buffer[offset] & 0xFF) << 8) + + (buffer[++offset] & 0xFF); + } } diff --git a/src/main/java/com/fasterxml/uuid/UUIDTimer.java b/src/main/java/com/fasterxml/uuid/UUIDTimer.java index 789bcf4..203f934 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDTimer.java +++ b/src/main/java/com/fasterxml/uuid/UUIDTimer.java @@ -320,6 +320,20 @@ public synchronized long getTimestamp() return systime; } + /** + * Converts a UUID v1 or v6 timestamp (where unit is 100 nanoseconds), + * to Unix epoch timestamp (milliseconds since 01-Jan-1970 UTC) + * + * @param timestamp Timestamp used to create UUID versions 1 and 6 + * + * @return Unix epoch timestamp + * + * @since 5.1 + */ + public static long timestampToEpoch(long timestamp) { + return (timestamp - kClockOffset) / kClockMultiplierL; + } + /* /********************************************************************** /* Test-support methods @@ -358,7 +372,7 @@ protected final void getAndSetTimestamp(byte[] uuidBytes) /********************************************************************** */ - private final static int MAX_WAIT_COUNT = 50; + private static final int MAX_WAIT_COUNT = 50; /** * Simple utility method to use to wait for couple of milliseconds, diff --git a/src/main/java/com/fasterxml/uuid/UUIDType.java b/src/main/java/com/fasterxml/uuid/UUIDType.java index ee33b2f..1794b86 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDType.java +++ b/src/main/java/com/fasterxml/uuid/UUIDType.java @@ -2,7 +2,7 @@ /** * Enumeration of different flavors of UUIDs: 5 specified by specs - * (RFC-4122) + * (RFC-9562) * and one * virtual entry ("UNKNOWN") to represent invalid one that consists of * all zero bites diff --git a/src/main/java/com/fasterxml/uuid/impl/LazyRandom.java b/src/main/java/com/fasterxml/uuid/impl/LazyRandom.java index 5d53b0f..40a8ba2 100644 --- a/src/main/java/com/fasterxml/uuid/impl/LazyRandom.java +++ b/src/main/java/com/fasterxml/uuid/impl/LazyRandom.java @@ -6,12 +6,26 @@ * Trivial helper class that uses class loading as synchronization * mechanism for lazy instantiation of the shared secure random * instance. + *

+ * Since 5.0 has been lazily created to avoid issues with native-generation + * tools like Graal. */ public final class LazyRandom { - private final static SecureRandom shared = new SecureRandom(); + private static final Object lock = new Object(); + private static volatile SecureRandom shared; public static SecureRandom sharedSecureRandom() { - return shared; + if (shared != null) { + return shared; + } + synchronized (lock) { + SecureRandom result = shared; + if (result == null) { + shared = result = new SecureRandom(); + } + + return result; + } } } \ No newline at end of file diff --git a/src/main/java/com/fasterxml/uuid/impl/NameBasedGenerator.java b/src/main/java/com/fasterxml/uuid/impl/NameBasedGenerator.java index 9e76a56..096d32f 100644 --- a/src/main/java/com/fasterxml/uuid/impl/NameBasedGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/NameBasedGenerator.java @@ -1,6 +1,7 @@ package com.fasterxml.uuid.impl; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.UUID; @@ -18,10 +19,7 @@ */ public class NameBasedGenerator extends StringArgGenerator { - public final static Charset _utf8; - static { - _utf8 = Charset.forName("UTF-8"); - } + public final static Charset _utf8 = StandardCharsets.UTF_8; private final LoggerFacade _logger = LoggerFacade.getLogger(getClass()); @@ -118,7 +116,7 @@ public NameBasedGenerator(UUID namespace, MessageDigest digester, UUIDType type) @Override public UUID generate(String name) { - // !!! TODO: 14-Oct-2010, tatu: can repurpose faster UTF-8 encoding from Jackson + // !!! TODO: 14-Oct-2010, tatu: could re-purpose faster UTF-8 encoding from Jackson return generate(name.getBytes(_utf8)); } diff --git a/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java b/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java index 776ea4b..241b2ed 100644 --- a/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java @@ -23,12 +23,6 @@ */ public class RandomBasedGenerator extends NoArgGenerator { - /** - * Default shared random number generator, used if no random number generator - * is explicitly specified for instance - */ - protected static Random _sharedRandom = null; - /** * Random number generator that this generator uses. */ @@ -51,10 +45,8 @@ public RandomBasedGenerator(Random rnd) { if (rnd == null) { rnd = LazyRandom.sharedSecureRandom(); - _secureRandom = true; - } else { - _secureRandom = (rnd instanceof SecureRandom); } + _secureRandom = (rnd instanceof SecureRandom); _random = rnd; } @@ -92,26 +84,4 @@ public UUID generate() } return UUIDUtil.constructUUID(UUIDType.RANDOM_BASED, r1, r2); } - - /* - /********************************************************************** - /* Internal methods - /********************************************************************** - */ - - protected final static long _toLong(byte[] buffer, int offset) - { - long l1 = _toInt(buffer, offset); - long l2 = _toInt(buffer, offset+4); - long l = (l1 << 32) + ((l2 << 32) >>> 32); - return l; - } - - private final static long _toInt(byte[] buffer, int offset) - { - return (buffer[offset] << 24) - + ((buffer[++offset] & 0xFF) << 16) - + ((buffer[++offset] & 0xFF) << 8) - + (buffer[++offset] & 0xFF); - } } diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java index b835492..42157ce 100644 --- a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -128,7 +128,7 @@ public UUID construct(long rawTimestamp) if (c) { byte temp = _lastEntropy[i]; temp = (byte) (temp + 0x01); - c = _lastEntropy[i] == (byte) 0xff && c; + c = _lastEntropy[i] == (byte) 0xff; _lastEntropy[i] = temp; } } @@ -144,32 +144,4 @@ public UUID construct(long rawTimestamp) lock.unlock(); } } - - /* - /********************************************************************** - /* Internal methods - /********************************************************************** - */ - - protected final static long _toLong(byte[] buffer, int offset) - { - long l1 = _toInt(buffer, offset); - long l2 = _toInt(buffer, offset+4); - long l = (l1 << 32) + ((l2 << 32) >>> 32); - return l; - } - - private final static long _toInt(byte[] buffer, int offset) - { - return (buffer[offset] << 24) - + ((buffer[++offset] & 0xFF) << 16) - + ((buffer[++offset] & 0xFF) << 8) - + (buffer[++offset] & 0xFF); - } - - private final static long _toShort(byte[] buffer, int offset) - { - return ((buffer[offset] & 0xFF) << 8) - + (buffer[++offset] & 0xFF); - } } diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochRandomGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochRandomGenerator.java new file mode 100644 index 0000000..53b9cbc --- /dev/null +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochRandomGenerator.java @@ -0,0 +1,129 @@ +package com.fasterxml.uuid.impl; + +import com.fasterxml.uuid.NoArgGenerator; +import com.fasterxml.uuid.UUIDClock; +import com.fasterxml.uuid.UUIDType; + +import java.security.SecureRandom; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implementation of UUID generator that uses time/location based generation + * method field from the Unix Epoch timestamp source - the number of + * milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded. + * This is usually referred to as "Version 7". + * In addition to that random part is regenerated for every new UUID. + * This removes possibilities to have almost similar UUID, when calls + * to generate are made within same millisecond. + *

+ * As all JUG provided implementations, this generator is fully thread-safe. + * Additionally it can also be made externally synchronized with other instances + * (even ones running on other JVMs); to do this, use + * {@link com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer} (or + * equivalent). + * + * @since 5.0 + */ +public class TimeBasedEpochRandomGenerator extends NoArgGenerator +{ + private static final int ENTROPY_BYTE_LENGTH = 10; + + /* + /********************************************************************** + /* Configuration + /********************************************************************** + */ + + /** + * Random number generator that this generator uses. + */ + protected final Random _random; + + /** + * Underlying {@link UUIDClock} used for accessing current time, to use for + * generation. + */ + protected final UUIDClock _clock; + + private final byte[] _lastEntropy = new byte[ENTROPY_BYTE_LENGTH]; + private final Lock lock = new ReentrantLock(); + + /* + /********************************************************************** + /* Construction + /********************************************************************** + */ + + /** + * @param rnd Random number generator to use for generating UUIDs; if null, + * shared default generator is used. Note that it is strongly recommend to + * use a good (pseudo) random number generator; for example, JDK's + * {@link SecureRandom}. + */ + public TimeBasedEpochRandomGenerator(Random rnd) { + this(rnd, UUIDClock.systemTimeClock()); + } + + /** + * @param rnd Random number generator to use for generating UUIDs; if null, + * shared default generator is used. Note that it is strongly recommend to + * use a good (pseudo) random number generator; for example, JDK's + * {@link SecureRandom}. + * @param clock clock Object used for accessing current time to use for generation + */ + public TimeBasedEpochRandomGenerator(Random rnd, UUIDClock clock) + { + if (rnd == null) { + rnd = LazyRandom.sharedSecureRandom(); + } + _random = rnd; + _clock = clock; + } + + /* + /********************************************************************** + /* Access to config + /********************************************************************** + */ + + @Override + public UUIDType getType() { return UUIDType.TIME_BASED_EPOCH; } + + /* + /********************************************************************** + /* UUID generation + /********************************************************************** + */ + + @Override + public UUID generate() + { + return construct(_clock.currentTimeMillis()); + } + + /** + * Method that will construct actual {@link UUID} instance for given + * unix epoch timestamp: called by {@link #generate()} but may alternatively be + * called directly to construct an instance with known timestamp. + * NOTE: calling this method directly produces somewhat distinct UUIDs as + * "entropy" value is still generated as necessary to avoid producing same + * {@link UUID} even if same timestamp is being passed. + * + * @param rawTimestamp unix epoch millis + * + * @return unix epoch time based UUID + */ + public UUID construct(long rawTimestamp) + { + lock.lock(); + try { + _random.nextBytes(_lastEntropy); + return UUIDUtil.constructUUID(UUIDType.TIME_BASED_EPOCH, (rawTimestamp << 16) | _toShort(_lastEntropy, 0), _toLong(_lastEntropy, 2)); + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java index 41c8984..e66041f 100644 --- a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java +++ b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java @@ -2,6 +2,7 @@ import java.util.UUID; +import com.fasterxml.uuid.UUIDTimer; import com.fasterxml.uuid.UUIDType; public class UUIDUtil @@ -45,7 +46,7 @@ public UUIDUtil() { } /** * Accessor for so-call "Nil UUID" (see - * RFC 4122/4.1.7; + * RFC 9562, #5.9; * one that is all zeroes. * * @since 4.1 @@ -58,7 +59,7 @@ public static UUID nilUUID() { /** * Accessor for so-call "Max UUID" (see - * UUID 6 draft; + * RFC-9562, #5.10); * one that is all one bits * * @since 4.1 @@ -353,4 +354,82 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset) throw new IllegalArgumentException("Invalid offset ("+offset+") passed: not enough room in byte array (need 16 bytes)"); } } + + /** + * Extract 64-bit timestamp from time-based UUIDs (if time-based type); + * returns 0 for other types. + * + * @param uuid uuid timestamp to extract from + * + * @return Unix timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps + * + * @since 5.0 + */ + public static long extractTimestamp(UUID uuid) + { + UUIDType type = typeOf(uuid); + if (type == null) { + // Likely null UUID: + return 0L; + } + switch (type) { + case NAME_BASED_SHA1: + case UNKNOWN: + case DCE: + case RANDOM_BASED: + case FREE_FORM: + case NAME_BASED_MD5: + return 0L; + case TIME_BASED: + return UUIDTimer.timestampToEpoch(_getRawTimestampFromUuidV1(uuid)); + case TIME_BASED_REORDERED: + return UUIDTimer.timestampToEpoch(_getRawTimestampFromUuidV6(uuid)); + case TIME_BASED_EPOCH: + return _getRawTimestampFromUuidV7(uuid); + default: + throw new IllegalArgumentException("Invalid `UUID`: unexpected type " + type); + } + } + + /** + * Get raw timestamp, used to create the UUID v1 + *

+ * NOTE: no verification is done to ensure UUID given is of version 1. + * + * @param uuid uuid, to extract timestamp from + * @return timestamp, used to create uuid v1 + */ + static long _getRawTimestampFromUuidV1(UUID uuid) { + long mostSignificantBits = uuid.getMostSignificantBits(); + mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1110_1111_1111_1111L; + long low = mostSignificantBits >>> 32; + long lowOfHigher = mostSignificantBits & 0xFFFF0000L; + lowOfHigher = lowOfHigher >>> 16; + long highOfHigher = mostSignificantBits & 0xFFFFL; + return highOfHigher << 48 | lowOfHigher << 32 | low; + } + + /** + * Get raw timestamp, used to create the UUID v6. + *

+ * NOTE: no verification is done to ensure UUID given is of version 6. + * + * @param uuid uuid, to extract timestamp from + * @return timestamp, used to create uuid v6 + */ + static long _getRawTimestampFromUuidV6(UUID uuid) { + long mostSignificantBits = uuid.getMostSignificantBits(); + mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L; + long lowL = mostSignificantBits & 0xFFFL; + long lowH = mostSignificantBits & 0xFFFF0000L; + lowH = lowH >>> 16; + long high = mostSignificantBits & 0xFFFFFFFF00000000L; + return high >>> 4 | lowH << 12 | lowL; + } + + static long _getRawTimestampFromUuidV7(UUID uuid) { + long mostSignificantBits = uuid.getMostSignificantBits(); + mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L; + return mostSignificantBits >>> 16; + } } diff --git a/src/main/java/perf/MeasurePerformance.java b/src/main/java/perf/MeasurePerformance.java index 570bc41..71d1928 100644 --- a/src/main/java/perf/MeasurePerformance.java +++ b/src/main/java/perf/MeasurePerformance.java @@ -1,5 +1,6 @@ package perf; +import java.nio.charset.StandardCharsets; import java.util.UUID; import com.fasterxml.uuid.*; @@ -19,20 +20,26 @@ */ public class MeasurePerformance { - // Let's generate quarter million UUIDs per test - - private static final int ROUNDS = 250; - private static final int COUNT = 1000; - + // also: let's just use a single name for name-based, to avoid extra overhead: - final String NAME = "http://www.cowtowncoder.com/blog/blog.html"; - final byte[] NAME_BYTES; + private final static String NAME_STRING = "http://www.cowtowncoder.com/blog/blog.html"; - public MeasurePerformance() throws java.io.IOException - { - NAME_BYTES = NAME.getBytes("UTF-8"); + private final static byte[] NAME_BYTES = NAME_STRING.getBytes(StandardCharsets.UTF_8); + + // Let's generate 50k UUIDs per test round + private static final int COUNT = 1000; + private static final int DEFAULT_ROUNDS = 50; + + private final int rounds; + private final boolean runForever; + + public MeasurePerformance() { this(DEFAULT_ROUNDS, true); } + + public MeasurePerformance(int rounds, boolean runForever) { + this.rounds = rounds; + this.runForever = runForever; } - + public void test() throws Exception { int i = 0; @@ -53,8 +60,11 @@ public void test() throws Exception new com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer()); final StringArgGenerator nameGen = Generators.nameBasedGenerator(namespaceForNamed); - while (true) { - try { Thread.sleep(100L); } catch (InterruptedException ie) { } + boolean running = true; + final long sleepTime = runForever ? 350L : 1L; + + while (running) { + Thread.sleep(sleepTime); int round = (i++ % 7); long curr = System.currentTimeMillis(); @@ -65,44 +75,49 @@ public void test() throws Exception case 0: msg = "JDK, random"; - testJDK(uuids, ROUNDS); + testJDK(uuids, rounds); break; case 1: msg = "JDK, name"; - testJDKNames(uuids, ROUNDS); + testJDKNames(uuids, rounds); break; case 2: msg = "Jug, time-based (non-sync)"; - testTimeBased(uuids, ROUNDS, timeGenPlain); + testTimeBased(uuids, rounds, timeGenPlain); break; case 3: msg = "Jug, time-based (SYNC)"; - testTimeBased(uuids, ROUNDS, timeGenSynced); + testTimeBased(uuids, rounds, timeGenSynced); break; case 4: msg = "Jug, SecureRandom"; - testRandom(uuids, ROUNDS, secureRandomGen); + testRandom(uuids, rounds, secureRandomGen); break; case 5: msg = "Jug, java.util.Random"; - testRandom(uuids, ROUNDS, utilRandomGen); + testRandom(uuids, rounds, utilRandomGen); break; case 6: msg = "Jug, name-based"; - testNameBased(uuids, ROUNDS, nameGen); + testNameBased(uuids, rounds, nameGen); + + // Last one, quit unless running forever + if (!runForever) { + running = false; + } break; /* case 7: msg = "http://johannburkard.de/software/uuid/"; - testUUID32(uuids, ROUNDS); + testUUID32(uuids, rounds); break; */ @@ -143,7 +158,7 @@ private final void testJDKNames(Object[] uuids, int rounds) throws java.io.IOExc { while (--rounds >= 0) { for (int i = 0, len = uuids.length; i < len; ++i) { - final byte[] nameBytes = NAME.getBytes("UTF-8"); + final byte[] nameBytes = NAME_BYTES; uuids[i] = UUID.nameUUIDFromBytes(nameBytes); } } @@ -171,13 +186,13 @@ private final void testNameBased(Object[] uuids, int rounds, StringArgGenerator { while (--rounds >= 0) { for (int i = 0, len = uuids.length; i < len; ++i) { - uuids[i] = uuidGen.generate(NAME); + uuids[i] = uuidGen.generate(NAME_STRING); } } } public static void main(String[] args) throws Exception { - new MeasurePerformance().test(); + new MeasurePerformance(DEFAULT_ROUNDS, true).test(); } } diff --git a/src/test/java/com/fasterxml/uuid/JugNamedTest.java b/src/test/java/com/fasterxml/uuid/JugNamedTest.java new file mode 100644 index 0000000..2352a94 --- /dev/null +++ b/src/test/java/com/fasterxml/uuid/JugNamedTest.java @@ -0,0 +1,178 @@ +package com.fasterxml.uuid; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +@RunWith(Parameterized.class) +public class JugNamedTest { + @Parameterized.Parameter + public UseCase useCase; + + private PrintStream oldStrOut; + private PrintStream oldStrErr; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private Jug jug_underTest; + + @Before + public void setup() { + jug_underTest = new Jug();oldStrOut = System.out; + oldStrErr = System.err; + PrintStream stubbedStream = new PrintStream(outContent); + System.setOut(stubbedStream); + PrintStream stubbedErrStream = new PrintStream(errContent); + System.setErr(stubbedErrStream); + } + + @After + public void cleanup() { + System.setOut(oldStrOut); + System.setErr(oldStrErr); + } + + @Test + public void run_shouldProduceUUID() { + // given + + // when + List arguments = useCase.getArgs(); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenCount3_shouldProduceUUID() { + // given + + // when + List arguments = useCase.getArgs(); + arguments.add(0, "-c"); + arguments.add(1, "3"); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String[] actualUuids = outContent.toString().split("\n"); + for(String actualUuid: actualUuids) { + assertEquals(UUID.class, + UUID.fromString(actualUuid).getClass()); + } + } + + @Test + public void run_givenPerformance_shouldProducePerformanceInfo() { + // given + + // when + List arguments = useCase.getArgs(); + arguments.add(0, "-p"); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = outContent.toString(); + + assertThat(actualOutput, containsString("Performance: took")); + } + @Test + public void run_givenHelp_shouldProduceHelpInfo() { + // given + + // when + List arguments = useCase.getArgs(); + arguments.add(0, "-h"); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = errContent.toString(); + + assertThat(actualOutput, containsString("Usage: java")); + } + + @Test + public void run_givenVerbose_shouldProduceExtraInfo() { + // given + + // when + List arguments = useCase.getArgs(); + arguments.add(0, "-v"); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = outContent.toString(); + + assertThat(actualOutput, containsString("Done.")); + } + + @Test + public void run_givenVerboseAndPerformance_shouldProduceExtraInfo() { + // given + + // when + List arguments = useCase.getArgs(); + arguments.add(0, "-v"); + arguments.add(1, "-p"); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = outContent.toString(); + + assertThat(actualOutput, containsString("Done.")); + assertThat(actualOutput, containsString("Performance: took")); + } + + @Parameterized.Parameters(name = "{index} -> {0}") + public static List useCases() { + return Arrays.asList( + new UseCase("n", "-n", "world", "-s", "url"), + new UseCase("n", "-n", "world", "-s", "dns") + ); + } + + private static class UseCase { + private final String type; + private String[] options = new String[]{}; + + public UseCase(String type, String...options) { + this.type = type; + if (options != null) { + this.options = options; + } + } + + public List getArgs() { + List arguments = new ArrayList<>(Arrays.asList(options)); + arguments.add(type); + return arguments; + } + + @Override + public String toString() { + if (options.length == 0) { + return String.format("type: %s, options: no options", type); + } else { + return String.format("type: %s, options: %s", type, String.join(", ", options)); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/uuid/JugNoArgsTest.java b/src/test/java/com/fasterxml/uuid/JugNoArgsTest.java new file mode 100644 index 0000000..d000105 --- /dev/null +++ b/src/test/java/com/fasterxml/uuid/JugNoArgsTest.java @@ -0,0 +1,217 @@ +package com.fasterxml.uuid; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.util.*; + +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.*; + +@RunWith(Parameterized.class) +public class JugNoArgsTest { + @Parameterized.Parameter + public String useCase; + + private PrintStream oldStrOut; + private PrintStream oldStrErr; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private Jug jug_underTest; + + @Before + public void setup() { + jug_underTest = new Jug(); + oldStrOut = System.out; + oldStrErr = System.err; + PrintStream stubbedStream = new PrintStream(outContent); + System.setOut(stubbedStream); + PrintStream stubbedErrStream = new PrintStream(errContent); + System.setErr(stubbedErrStream); + } + + @After + public void cleanup() { + System.setOut(oldStrOut); + System.setErr(oldStrErr); + } + + @Test + public void run_givenNoOptions_shouldProduceUUID() { + // given + + // when + jug_underTest.run(new String[]{useCase}); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenCount1_shouldProduceUUID() { + // given + + // when + List arguments = new ArrayList<>(Arrays.asList("-c", "1")); + arguments.add(useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenCount2_shouldProduce2UUIDs() { + // given + + // when + List arguments = new ArrayList<>(Arrays.asList("-c", "2")); + arguments.add(useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String[] actualUuids = outContent.toString().split("\n"); + assertEquals(2, actualUuids.length); + + for(String actualUuid: actualUuids) { + assertEquals(UUID.class, + UUID.fromString(actualUuid).getClass()); + } + } + + @Test + public void run_givenEthernet_shouldProduceUUID() { + // given + + // when + List arguments = new ArrayList<>(Arrays.asList("-e", ":::::")); + arguments.add(useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenName_shouldProduceUUID() { + // given + + // when + List arguments = new ArrayList<>(Arrays.asList("-n", "hello")); + arguments.add(useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenDnsNameSpace_shouldProduceUUID() { + // given + + // when + List arguments = new ArrayList<>(Arrays.asList("-s", "dns")); + arguments.add(useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenUrlNameSpace_shouldProduceUUID() { + // given + + // when + List arguments = new ArrayList<>(Arrays.asList("-s", "url")); + arguments.add(useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then - if it is a UUID then we should be able to parse it back out + String actualUuid = outContent.toString(); + assertEquals('\n', actualUuid.charAt(actualUuid.length() - 1)); + + assertEquals(UUID.class, + UUID.fromString(actualUuid.substring(0, actualUuid.length() - 1)).getClass()); + } + + @Test + public void run_givenPerformance_shouldProducePerformanceInfo() { + // given + + // when + List arguments = Arrays.asList("-p", useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = outContent.toString(); + + assertThat(actualOutput, containsString("Performance: took")); + } + + @Test + public void run_givenHelp_shouldProduceHelpInfo() { + // given + + // when + List arguments = Arrays.asList("-h", useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = errContent.toString(); + + assertThat(actualOutput, containsString("Usage: java")); + } + + @Test + public void run_givenVerbose_shouldProduceExtraInfo() { + // given + + // when + List arguments = Arrays.asList("-v", useCase); + jug_underTest.run(arguments.toArray((String[]) Array.newInstance(String.class, 0))); + + // then + String actualOutput = outContent.toString(); + + assertThat(actualOutput, containsString("Done.")); + } + + @Parameterized.Parameters(name = "{index} -> type: {0}") + public static List useCases() { + return Arrays.asList( + "t", + "o", + "r", + "e", + "m" + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java b/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java index 6103a66..3d79ec9 100644 --- a/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java +++ b/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java @@ -22,6 +22,7 @@ import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; +import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator; import junit.framework.TestCase; public class UUIDComparatorTest diff --git a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java index 59407ff..1b86416 100644 --- a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java +++ b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java @@ -32,6 +32,7 @@ import com.fasterxml.uuid.impl.NameBasedGenerator; import com.fasterxml.uuid.impl.RandomBasedGenerator; import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; +import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator; import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator; import com.fasterxml.uuid.impl.TimeBasedGenerator; @@ -119,7 +120,8 @@ public void testGenerateRandomBasedUUID() // we need a instance to use RandomBasedGenerator uuid_gen = Generators.randomBasedGenerator(); - + assertEquals(uuid_gen.getType(), UUIDType.RANDOM_BASED); + // for the random UUID generator, we will generate a bunch of // random UUIDs UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; @@ -153,7 +155,8 @@ public void testGenerateTimeBasedUUID() // we need a instance to use TimeBasedGenerator uuid_gen = Generators.timeBasedGenerator(); - + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED); + // first check that given a number of calls to generateTimeBasedUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting @@ -202,7 +205,8 @@ public void testGenerateTimeBasedUUIDWithEthernetAddress() // we need a instance to use TimeBasedGenerator uuid_gen = Generators.timeBasedGenerator(ethernet_address); - + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED); + // check that given a number of calls to generateTimeBasedUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting @@ -261,6 +265,7 @@ public void testGenerateTimeBasedEpochUUID() throws Exception // we need a instance to use TimeBasedEpochGenerator uuid_gen = Generators.timeBasedEpochGenerator(entropy); + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_EPOCH); // first check that given a number of calls to generateTimeBasedEpochUUID, // all returned UUIDs order after the last returned UUID @@ -299,6 +304,162 @@ public void testGenerateTimeBasedEpochUUID() throws Exception checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); } + /** + * Test of generateTimeBasedEpochUUID() method with UUIDClock instance, + * of class com.fasterxml.uuid.UUIDGenerator. + */ + public void testGenerateTimeBasedEpochUUIDWithUUIDClock() throws Exception + { + // this test will attempt to check for reasonable behavior of the + // generateTimeBasedUUID method + + Random entropy = new Random(0x666); + + // we need a instance to use + TimeBasedEpochGenerator uuid_gen = Generators.timeBasedEpochGenerator(null, UUIDClock.systemTimeClock()); + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_EPOCH); + + // first check that given a number of calls to generateTimeBasedEpochUUID, + // all returned UUIDs order after the last returned UUID + // we'll check this by generating the UUIDs into one array and sorting + // then in another and checking the order of the two match + // change the number in the array statement if you want more or less + // UUIDs to be generated and tested + UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; + + // before we generate all the uuids, lets get the start time + long start_time = System.currentTimeMillis(); + Thread.sleep(2); // Clean start time + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) { + uuid_array[i] = uuid_gen.generate(); + } + + // now capture the end time + long end_time = System.currentTimeMillis(); + Thread.sleep(2); // Clean end time + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + + // check that all the uuids were correct variant and version (type-1) + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.TIME_BASED_EPOCH); + + // check that all the uuids were generated with correct order + checkUUIDArrayForCorrectOrdering(uuid_array); + + // check that all uuids were unique + checkUUIDArrayForUniqueness(uuid_array); + + // check that all uuids have timestamps between the start and end time + checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); + } + + /** + * Test of generateTimeBasedEpochRandomUUID() method, + * of class com.fasterxml.uuid.UUIDGenerator. + */ + public void testGenerateTimeBasedEpochRandomUUID() throws Exception + { + // this test will attempt to check for reasonable behavior of the + // generateTimeBasedRandomUUID method + + Random entropy = new Random(0x666); + + // we need a instance to use + TimeBasedEpochRandomGenerator uuid_gen = Generators.timeBasedEpochRandomGenerator(entropy); + + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_EPOCH); + // first check that given a number of calls to generateTimeBasedEpochUUID, + // all returned UUIDs order after the last returned UUID + // we'll check this by generating the UUIDs into one array and sorting + // then in another and checking the order of the two match + // change the number in the array statement if you want more or less + // UUIDs to be generated and tested + UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; + + // before we generate all the uuids, lets get the start time + long start_time = System.currentTimeMillis(); + Thread.sleep(2); // Clean start time + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) { + uuid_array[i] = uuid_gen.generate(); + } + + // now capture the end time + long end_time = System.currentTimeMillis(); + Thread.sleep(2); // Clean end time + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + + // check that all the uuids were correct variant and version (type-1) + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.TIME_BASED_EPOCH); + + // check that all uuids were unique + // NOTE: technically, this test 'could' fail, but statistically + // speaking it should be extremely unlikely unless the implementation + // of (Secure)Random is bad + checkUUIDArrayForUniqueness(uuid_array); + + // check that all uuids have timestamps between the start and end time + checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); + } + + /** + * Test of generateTimeBasedEpochRandomUUID() method with UUIDClock instance, + * of class com.fasterxml.uuid.UUIDGenerator. + */ + public void testGenerateTimeBasedEpochRandomUUIDWithUUIDClock() throws Exception + { + // this test will attempt to check for reasonable behavior of the + // generateTimeBasedRandomUUID method + + Random entropy = new Random(0x666); + + // we need a instance to use + TimeBasedEpochRandomGenerator uuid_gen = Generators.timeBasedEpochRandomGenerator(null, UUIDClock.systemTimeClock()); + + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_EPOCH); + // first check that given a number of calls to generateTimeBasedEpochUUID, + // all returned UUIDs order after the last returned UUID + // we'll check this by generating the UUIDs into one array and sorting + // then in another and checking the order of the two match + // change the number in the array statement if you want more or less + // UUIDs to be generated and tested + UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; + + // before we generate all the uuids, lets get the start time + long start_time = System.currentTimeMillis(); + Thread.sleep(2); // Clean start time + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) { + uuid_array[i] = uuid_gen.generate(); + } + + // now capture the end time + long end_time = System.currentTimeMillis(); + Thread.sleep(2); // Clean end time + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + + // check that all the uuids were correct variant and version (type-1) + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.TIME_BASED_EPOCH); + + // check that all uuids were unique + // NOTE: technically, this test 'could' fail, but statistically + // speaking it should be extremely unlikely unless the implementation + // of (Secure)Random is bad + checkUUIDArrayForUniqueness(uuid_array); + + // check that all uuids have timestamps between the start and end time + checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); + } + // [#70]: allow use of custom UUIDClock public void testGenerateTimeBasedEpochUUIDWithFixedClock() throws Exception { @@ -342,7 +503,8 @@ public void testGenerateNameBasedUUIDNameSpaceAndName() // we need a instance to use NameBasedGenerator uuid_gen = Generators.nameBasedGenerator(NameBasedGenerator.NAMESPACE_URL); - + assertEquals(uuid_gen.getType(), UUIDType.NAME_BASED_SHA1); + UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; // now create the array of uuids @@ -426,6 +588,7 @@ public void testGenerateNameBasedUUIDNameSpaceNameAndMessageDigest() // generateNameBasedUUID method NameBasedGenerator uuid_gen = Generators.nameBasedGenerator(NameBasedGenerator.NAMESPACE_URL, MESSAGE_DIGEST); + assertEquals(uuid_gen.getType(), UUIDType.NAME_BASED_MD5); UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; // now create the array of uuids @@ -491,6 +654,82 @@ public void testGenerateNameBasedUUIDNameSpaceNameAndMessageDigest() Arrays.equals(uuid_array, uuid_array2)); } + /** + * Test of generateNameBasedUUID() + * method, of class com.fasterxml.uuid.UUIDGenerator. + */ + public void testGenerateNameBasedUUIDWithDefaults() + { + // this test will attempt to check for reasonable behavior of the + // generateNameBasedUUID method + + NameBasedGenerator uuid_gen = Generators.nameBasedGenerator(); + assertEquals(uuid_gen.getType(), UUIDType.NAME_BASED_SHA1); + UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) + { + uuid_array[i] = uuid_gen.generate("test name"+i); + } + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + + // check that all the uuids were correct variant and version + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.NAME_BASED_SHA1); + + // check that all uuids were unique + checkUUIDArrayForUniqueness(uuid_array); + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) + { + uuid_array[i] = uuid_gen.generate("test name" + i); + } + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + + // check that all the uuids were correct variant and version + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.NAME_BASED_SHA1); + + // check that all uuids were unique + checkUUIDArrayForUniqueness(uuid_array); + + // now, lets make sure generating two sets of name based uuid with the + // same args always gives the same result + uuid_array = new UUID[SIZE_OF_TEST_ARRAY]; + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) { + uuid_array[i] = uuid_gen.generate("test name" + i); + } + + UUID uuid_array2[] = new UUID[SIZE_OF_TEST_ARRAY]; + + // now create the array of uuids + for (int i = 0; i < uuid_array2.length; i++) { + uuid_array2[i] = uuid_gen.generate("test name" + i); + } + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + checkUUIDArrayForNonNullUUIDs(uuid_array2); + + // check that all the uuids were correct variant and version + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.NAME_BASED_SHA1); + checkUUIDArrayForCorrectVariantAndVersion(uuid_array2, UUIDType.NAME_BASED_SHA1); + + // check that all uuids were unique + checkUUIDArrayForUniqueness(uuid_array); + checkUUIDArrayForUniqueness(uuid_array2); + + // check that both arrays are equal to one another + assertTrue("expected both arrays to be equal, they were not!", + Arrays.equals(uuid_array, uuid_array2)); + } + /** * Test of generateTimeBasedReorderedUUID() method, * of class com.fasterxml.uuid.UUIDGenerator. @@ -502,7 +741,8 @@ public void testGenerateTimeBasedReorderedUUID() // we need a instance to use TimeBasedReorderedGenerator uuid_gen = Generators.timeBasedReorderedGenerator(); - + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_REORDERED); + // first check that given a number of calls to generateTimeBasedReorderedUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting @@ -551,7 +791,8 @@ public void testGenerateTimeBasedReorderedUUIDWithEthernetAddress() // we need a instance to use TimeBasedReorderedGenerator uuid_gen = Generators.timeBasedReorderedGenerator(ethernet_address); - + assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_REORDERED); + // check that given a number of calls to generateTimeBasedReorderedUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting diff --git a/src/test/java/com/fasterxml/uuid/ext/LockedFileTest.java b/src/test/java/com/fasterxml/uuid/ext/LockedFileTest.java new file mode 100644 index 0000000..cb9db2e --- /dev/null +++ b/src/test/java/com/fasterxml/uuid/ext/LockedFileTest.java @@ -0,0 +1,261 @@ +package com.fasterxml.uuid.ext; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import static com.fasterxml.uuid.ext.LockedFile.READ_ERROR; +import static org.junit.Assert.*; + +public class LockedFileTest +{ + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void constructor_givenNull_shouldThrowNullPointerException() throws IOException { + try { + new LockedFile(null); + fail("This should have thrown a null pointer exception"); + } catch (NullPointerException nullPointerException) { + ; // good + } + } + + @Test + public void constructor_givenEmptyFile_shouldLeaveFileAsIs() throws IOException { + // given + File emptyFile = temporaryFolder.newFile(); + + // when + new LockedFile(emptyFile); + + // then + assertTrue(emptyFile.exists()); + assertTrue(emptyFile.canRead()); + assertTrue(emptyFile.canWrite()); + } + + @Test + public void constructor_givenNonExistentFile_shouldCreateANewFile() throws IOException { + // given + File blankFile = temporaryFolder.newFile(); + File nonExistentFile = new File(blankFile + ".nonexistent"); + + if (Files.exists(nonExistentFile.toPath())) { + fail("temp file should not exist"); + } + + // when + new LockedFile(nonExistentFile); + + // then - the nonexistent file now exists? + assertTrue(Files.exists(nonExistentFile.toPath())); + assertTrue(nonExistentFile.canRead()); + assertTrue(nonExistentFile.canWrite()); + } + + @Test + public void constructor_canOnlyTakeAFile_shouldThrowFileNotFoundException() throws IOException { + // given + File blankFolder = temporaryFolder.newFolder(); + + // when + try { + new LockedFile(blankFolder); + fail("This should not succeed"); + } catch (FileNotFoundException fileNotFoundException) { + // then + assertEquals( + String.format("%s (Is a directory)", blankFolder.getPath()), + fileNotFoundException.getMessage() + ); + } + } + + @Test + public void readStamp_givenEmptyFile_shouldReturnREADERROR() throws IOException { + // given + File emptyFile = temporaryFolder.newFile(); + + // when + LockedFile lockedFile = new LockedFile(emptyFile); + long stamp = lockedFile.readStamp(); + + // then + assertEquals(READ_ERROR, stamp); + } + + @Test + public void readStamp_givenGibberishFile_shouldReturnREADERROR() throws IOException { + // given + File gibberishFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(gibberishFile)) { + fileWriter.write(UUID.randomUUID().toString().substring(0, 22)); + fileWriter.flush(); + } + + assertEquals(22, Files.size(gibberishFile.toPath())); + + // when + LockedFile lockedFile = new LockedFile(gibberishFile); + long stamp = lockedFile.readStamp(); + + // then + assertEquals(READ_ERROR, stamp); + } + + @Test + public void readStamp_givenTimestampedFile_shouldReturnValueInside() throws IOException { + // given + File timeStampedFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(timeStampedFile)) { + // we are faking the timestamp format + fileWriter.write("[0x0000000000000001]"); + fileWriter.flush(); + } + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + long stamp = lockedFile.readStamp(); + + // then + long expectedTimestamp = 1; + assertEquals(expectedTimestamp, stamp); + } + + // test for overflows + @Test + public void readStamp_givenOverflowedDigitFile_shouldReturnREADERROR() throws IOException { + // given + File timeStampedFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(timeStampedFile)) { + // we are faking an overflowed timestamp + fileWriter.write("[0x10000000000000000]"); + fileWriter.flush(); + } + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + long stamp = lockedFile.readStamp(); + + // then + assertEquals(READ_ERROR, stamp); + } + + @Test + public void readStamp_givenMaxLongFile_shouldReturnLargeTimestamp() throws IOException { + // given + File timeStampedFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(timeStampedFile)) { + // we are faking an overflowed timestamp + fileWriter.write("[0x7fffffffffffffff]"); + fileWriter.flush(); + } + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + long stamp = lockedFile.readStamp(); + + // then + assertEquals(Long.MAX_VALUE, stamp); + } + + @Test + public void writeStamp_givenNegativeTimestamps_shouldThrowIOException() throws IOException { + // given + File timeStampedFile = temporaryFolder.newFile(); + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + try { + lockedFile.writeStamp(Long.MIN_VALUE); + fail("This should throw an exception"); + } catch (IOException ioException) { + // then + assertTrue(ioException.getMessage().contains("trying to overwrite existing value")); + assertTrue(ioException.getMessage().contains("with an earlier timestamp")); + } + } + + @Test + public void writeStamp_givenTimestampedFile_withLowerValue_shouldOverrideValue() throws IOException { + // given + String inputValue = "[0x0000000000000000]"; + long numericInputValue = 0L; + long newTimestamp = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + + File timeStampedFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(timeStampedFile)) { + fileWriter.write(inputValue); + fileWriter.flush(); + } + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + + lockedFile.writeStamp(newTimestamp); + long stamp = lockedFile.readStamp(); + + // then + assertNotEquals(numericInputValue, stamp); + assertEquals(newTimestamp, stamp); + } + + @Test + public void writeStamp_givenNewerTimestampedFile_writeNegativeTimestamp_shouldThrowException() throws IOException { + // given + String inputValue = "[0x7fffffffffffffff]"; + long newTimestamp = Long.MIN_VALUE; + + File timeStampedFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(timeStampedFile)) { + fileWriter.write(inputValue); + fileWriter.flush(); + } + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + + try { + lockedFile.writeStamp(newTimestamp); + fail("This should throw an exception"); + } catch (IOException ioException) { + // then + assertTrue(ioException.getMessage().contains("trying to overwrite existing value")); + assertTrue(ioException.getMessage().contains("with an earlier timestamp")); + } + } + + @Test + public void writeStamp_givenTimestampedFile_writeSameTimestamp_shouldLeaveFileAlone() throws IOException { + // given + String inputValue = "[0x7fffffffffffffff]"; + long numericInputValue = Long.MAX_VALUE; + long newTimestamp = Long.MAX_VALUE; + + File timeStampedFile = temporaryFolder.newFile(); + try(FileWriter fileWriter = new FileWriter(timeStampedFile)) { + fileWriter.write(inputValue); + fileWriter.flush(); + } + + // when + LockedFile lockedFile = new LockedFile(timeStampedFile); + + lockedFile.writeStamp(newTimestamp); + long stamp = lockedFile.readStamp(); + + // then + assertEquals(numericInputValue, stamp); + assertEquals(newTimestamp, stamp); + } +} diff --git a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java index 2e43517..b730c00 100644 --- a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java +++ b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java @@ -1,7 +1,10 @@ package com.fasterxml.uuid.impl; +import java.util.Random; import java.util.UUID; +import com.fasterxml.uuid.Generators; +import com.fasterxml.uuid.NoArgGenerator; import junit.framework.TestCase; /** @@ -13,6 +16,8 @@ */ public class UUIDUtilTest extends TestCase { + final static int TEST_REPS = 1_000_000; + public void testNilUUID() { UUID nil = UUIDUtil.nilUUID(); // Should be all zeroes: @@ -26,4 +31,74 @@ public void testMaxUUID() { assertEquals(~0, max.getMostSignificantBits()); assertEquals(~0, max.getLeastSignificantBits()); } + + public void testExtractTimestampUUIDTimeBased() { + TimeBasedGenerator generator = Generators.timeBasedGenerator(); + final Random rnd = new Random(1); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 4; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil._getRawTimestampFromUuidV1(uuid)); + } + } + + public void testExtractTimestampUUIDTimeBasedCurrentTimemillis() { + TimeBasedGenerator generator = Generators.timeBasedGenerator(); + long time = System.currentTimeMillis(); + UUID uuid2 = generator.generate(); + assertEquals(time, UUIDUtil.extractTimestamp(uuid2)); + } + + + public void testExtractTimestampUUIDTimeBasedReordered() { + TimeBasedReorderedGenerator generator = Generators.timeBasedReorderedGenerator(); + final Random rnd = new Random(2); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 4; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil._getRawTimestampFromUuidV6(uuid)); + } + } + + public void testExtractTimestampUUIDTimeBasedReorderedCurrentTimeMillis() { + NoArgGenerator generator = Generators.timeBasedReorderedGenerator(); + long time = System.currentTimeMillis(); + UUID uuid = generator.generate(); + assertEquals(time, UUIDUtil.extractTimestamp(uuid)); + } + + public void testExtractTimestampUUIDEpochBased() { + TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator(); + final Random rnd = new Random(3); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 16; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); + } + } + + public void testExtractTimestampUUIDEpochBasedCurrentTimeMillis() { + NoArgGenerator generator = Generators.timeBasedEpochGenerator(); + long time = System.currentTimeMillis(); + UUID uuid = generator.generate(); + assertEquals(time, UUIDUtil.extractTimestamp(uuid)); + } + + + public void testExtractTimestampUUIDEpochRandomBased() { + TimeBasedEpochRandomGenerator generator = Generators.timeBasedEpochRandomGenerator(); + final Random rnd = new Random(3); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 16; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); + } + } + + public void testExtractTimestampUUIDOnOtherValues() { + assertEquals(0L, UUIDUtil.extractTimestamp(null)); + assertEquals(0L, UUIDUtil.extractTimestamp(UUID.fromString("00000000-0000-0000-0000-000000000000"))); + assertEquals(0L, UUIDUtil.extractTimestamp(UUIDUtil.nilUUID())); + assertEquals(0L, UUIDUtil.extractTimestamp(UUIDUtil.maxUUID())); + } } diff --git a/src/test/java/perf/MeasurePerformanceTest.java b/src/test/java/perf/MeasurePerformanceTest.java new file mode 100644 index 0000000..fdc81c7 --- /dev/null +++ b/src/test/java/perf/MeasurePerformanceTest.java @@ -0,0 +1,17 @@ +package perf; + +import org.junit.Test; + +// Things we do for Code Coverage... altough "perf/MeasurePerformance.java" +// is only to be manually run, it is included in build, so +// we get code coverage whether we want it or not. So let's have +// a silly little driver to exercise it from unit tests and avoid dinging +// overall test coverage +public class MeasurePerformanceTest +{ + @Test + public void runMinimalPerfTest() throws Exception + { + new MeasurePerformance(10, false).test(); + } +}