All typed UUIDs implemented (except v2).
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Jan 2022 05:47:10 +0000 (06:47 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Jan 2022 05:47:10 +0000 (06:47 +0100)
13 files changed:
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/UnkownNameUuid.java [deleted file]
org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java

index 685fe0a07bb3ff2515f3b9cdddd598bd839883a9..38327223ff168173f62870eb93a293d7a63d7be9 100644 (file)
@@ -28,14 +28,14 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
        private NodeIdSupplier nodeIdSupplier;
 
        public AbstractAsyncUuidFactory() {
-               secureRandom = newSecureRandom();
+               secureRandom = createSecureRandom();
                timeUuidState = new ConcurrentTimeUuidState(secureRandom, null);
        }
        /*
         * ABSTRACT METHODS
         */
 
-       protected abstract SecureRandom newSecureRandom();
+       protected abstract SecureRandom createSecureRandom();
 
        /*
         * STATE
@@ -59,7 +59,7 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
        /*
         * SYNC OPERATIONS
         */
-       protected UUID newRandomUUIDStrong() {
+       protected UUID createRandomUUIDStrong() {
                SecureRandomParameters parameters = secureRandom.getParameters();
                if (parameters != null) {
                        if (parameters instanceof DrbgParameters.Instantiation) {
@@ -70,14 +70,14 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
                                }
                        }
                }
-               return newRandomUUID(secureRandom);
+               return createRandomUUID(secureRandom);
        }
 
        public UUID randomUUIDWeak() {
-               return newRandomUUID(ThreadLocalRandom.current());
+               return createRandomUUID(ThreadLocalRandom.current());
        }
 
-       protected UUID newTimeUUID() {
+       protected UUID createTimeUUID() {
                if (nodeIdSupplier == null)
                        throw new IllegalStateException("No node id supplier available");
                UUID uuid = new UUID(timeUuidState.getMostSignificantBits(), timeUuidState.getLeastSignificantBits());
@@ -126,21 +126,21 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
 
        @Override
        public ForkJoinTask<UUID> futureNameUUIDv5(UUID namespace, byte[] data) {
-               return submit(() -> newNameUUIDv5(namespace, data));
+               return submit(() -> createNameUUIDv5(namespace, data));
        }
 
        @Override
        public ForkJoinTask<UUID> futureNameUUIDv3(UUID namespace, byte[] data) {
-               return submit(() -> newNameUUIDv3(namespace, data));
+               return submit(() -> createNameUUIDv3(namespace, data));
        }
 
        @Override
        public ForkJoinTask<UUID> futureRandomUUIDStrong() {
-               return submit(this::newRandomUUIDStrong);
+               return submit(this::createRandomUUIDStrong);
        }
 
        @Override
        public ForkJoinTask<UUID> futureTimeUUID() {
-               return submit(this::newTimeUUID);
+               return submit(this::createTimeUUID);
        }
 }
index 75a6339b01681f48a1890468b4a56f303bbee43b..1bbc4391f248b599293ac4df74c97dccca675ab6 100644 (file)
@@ -23,7 +23,8 @@ public abstract class AbstractUuidFactory implements UuidFactory {
        private final static long MOST_SIG_VERSION1 = (1l << 12);
        private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
 
-       protected UUID newTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) {
+       @Deprecated
+       protected UUID createTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) {
                Objects.requireNonNull(node, "Node array cannot be null");
                if (node.length < offset + 6)
                        throw new IllegalArgumentException("Node array must be at least 6 bytes long");
@@ -53,19 +54,23 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return uuid;
        }
 
+       @Deprecated
        protected UUID timeUUID(Temporal time, long clockSequence, byte[] node, int offset) {
                // TODO add checks
                Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, time);
                // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
                long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100;
-               return newTimeUUID(timestamp, clockSequence, node, offset);
+               return createTimeUUID(timestamp, clockSequence, node, offset);
        }
 
        /*
         * NAME BASED (version 3 and 5)
         */
+       protected UUID createNameUUIDv5(UUID namespace, byte[] name) {
+               return createNameUUIDv5Static(namespace, name);
+       }
 
-       protected UUID newNameUUIDv5(UUID namespace, byte[] name) {
+       static UUID createNameUUIDv5Static(UUID namespace, byte[] name) {
                Objects.requireNonNull(namespace, "Namespace cannot be null");
                Objects.requireNonNull(name, "Name cannot be null");
 
@@ -78,7 +83,11 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return result;
        }
 
-       protected UUID newNameUUIDv3(UUID namespace, byte[] name) {
+       protected UUID createNameUUIDv3(UUID namespace, byte[] name) {
+               return createNameUUIDv3Static(namespace, name);
+       }
+
+       static UUID createNameUUIDv3Static(UUID namespace, byte[] name) {
                Objects.requireNonNull(namespace, "Namespace cannot be null");
                Objects.requireNonNull(name, "Name cannot be null");
 
@@ -91,7 +100,7 @@ public abstract class AbstractUuidFactory implements UuidFactory {
        /*
         * RANDOM v4
         */
-       protected UUID newRandomUUID(Random random) {
+       protected UUID createRandomUUID(Random random) {
                byte[] arr = new byte[16];
                random.nextBytes(arr);
                arr[6] &= 0x0f;
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java
new file mode 100644 (file)
index 0000000..b883e0d
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/** A name based UUID (v3 or v5) whose construction values are not known. */
+public class BasicNameUuid extends TypedUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       public BasicNameUuid(UUID uuid) {
+               super(uuid);
+               if ((uuid.version() != 5 && uuid.version() != 3) || uuid.variant() != 2)
+                       throw new IllegalArgumentException("The provided UUID is not a name-based UUID.");
+       }
+
+       /**
+        * Always returns <code>true</true> since it is unknown from which values it was
+        * constructed..
+        */
+       @Override
+       public boolean isOpaque() {
+               return true;
+       }
+
+       /**
+        * Whether the hash of this name UUID was generated with SHA-1 (v5) or with MD5
+        * (v3).
+        */
+       public boolean isSha1() {
+               return uuid.version() == 5;
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java
new file mode 100644 (file)
index 0000000..6771c7e
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/**
+ * A name UUID whose binary data used for its construction is known. A new byte
+ * array is created and it is copied when retrieved, so this would be
+ * inefficient and memory consuming for big data amounts. This rather meant to
+ * be used for small binary data, such as certificates, etc.
+ */
+public class BinaryNameUuid extends BasicNameUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       protected final TypedUuid namespace;
+       protected final byte[] bytes;
+
+       /** Static builder (a {@link TypedUuidFactory} may be more efficient). */
+       public BinaryNameUuid(TypedUuid namespace, byte[] bytes, boolean sha1) {
+               this(sha1 ? AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, bytes)
+                               : AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, bytes), namespace, bytes);
+       }
+
+       /**
+        * Since no check is performed, the constructor is protected so that the object
+        * can only be built by the default methods of {@link TypedUuidFactory} (in this
+        * package) or by extending the class.
+        */
+       protected BinaryNameUuid(UUID uuid, TypedUuid namespace, byte[] bytes) {
+               super(uuid);
+               this.namespace = namespace;
+               this.bytes = new byte[bytes.length];
+               System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);
+       }
+
+       /** The namespace used to build this UUID. */
+       public final TypedUuid getNamespace() {
+               return namespace;
+       }
+
+       /**
+        * A <strong>copy</strong> of the bytes which have been hashed. In order to
+        * access the byte array directly, the class must be extended.
+        */
+       public final byte[] getBytes() {
+               byte[] bytes = new byte[this.bytes.length];
+               System.arraycopy(this.bytes, 0, bytes, 0, this.bytes.length);
+               return bytes;
+       }
+
+       /** Always returns <code>false</code> since the construction value is known. */
+       @Override
+       public final boolean isOpaque() {
+               return false;
+       }
+
+}
index 6debd83b803426453c9e56b1594b6793bcaf6dcb..15245b2932f50074f374b8156b06d696ee77cf8a 100644 (file)
@@ -16,7 +16,7 @@ import java.util.UUID;
  * 
  * @see https://datatracker.ietf.org/doc/html/rfc4122
  */
-public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
+public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory {
        private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
 
        public ConcurrentUuidFactory(byte[] nodeId) {
@@ -60,7 +60,7 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
        }
 
        @Override
-       protected SecureRandom newSecureRandom() {
+       protected SecureRandom createSecureRandom() {
                SecureRandom secureRandom;
                try {
                        secureRandom = SecureRandom.getInstance("DRBG",
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java
new file mode 100644 (file)
index 0000000..a9a5af1
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/**
+ * A variant 6 {@link UUID}.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1
+ */
+public class GUID extends TypedUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       /** Constructor based on a {@link UUID}. */
+       public GUID(UUID uuid) {
+               super(uuid);
+               if (uuid.variant() != 6)
+                       throw new IllegalArgumentException("The provided UUID is not a GUID.");
+       }
+
+       /**
+        * Formats N, D, B, P are supported:
+        * <ul>
+        * <li>D: 1db31359-bdd8-5a0f-b672-30c247d582c5</li>
+        * <li>N: 1db31359bdd85a0fb67230c247d582c5</li>
+        * <li>B: {1db31359-bdd8-5a0f-b672-30c247d582c5}</li>
+        * <li>P: (1db31359-bdd8-5a0f-b672-30c247d582c5)</li>
+        * </ul>
+        * 
+        * @see https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring
+        */
+       public static String toString(UUID uuid, char format, boolean upperCase) {
+               String str;
+               switch (format) {
+               case 'D':
+                       str = uuid.toString();
+                       break;
+               case 'N':
+                       str = UuidBinaryUtils.toCompact(uuid);
+                       break;
+               case 'B':
+                       str = "{" + uuid.toString() + "}";
+                       break;
+               case 'P':
+                       str = "(" + uuid.toString() + ")";
+                       break;
+               default:
+                       throw new IllegalArgumentException("Unsupported format : " + format);
+               }
+               return upperCase ? str.toUpperCase() : str;
+       }
+
+}
index a1c64a1a12b80b55f512b6436245ae95839f9707..376eea4bcf671e7ec2c6d5a78622107ec308706b 100644 (file)
@@ -1,16 +1,38 @@
 package org.argeo.api.uuid;
 
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.UUID;
 
-/** A name {@link UUID} whose values used for its construction are known. */
-public class NameUuid extends UnkownNameUuid {
+/** A name UUID whose values used for its construction are known. */
+public class NameUuid extends BasicNameUuid {
        private static final long serialVersionUID = APM.SERIAL;
 
        protected final TypedUuid namespace;
        protected final String name;
        protected final Charset encoding;
 
+       /**
+        * Default static builder which creates a v5 (SHA1) name based {@link UUID},
+        * using UTF-8 encoding. Use
+        * {@link #NameUuid(TypedUuid, String, Charset, boolean)} for more options.
+        */
+       public NameUuid(TypedUuid namespace, String name) {
+               this(namespace, name, StandardCharsets.UTF_8, true);
+       }
+
+       /** Static builder (an {@link TypedUuidFactory} may be more efficient). */
+       public NameUuid(TypedUuid namespace, String name, Charset encoding, boolean sha1) {
+               this(sha1 ? AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, name.getBytes(encoding))
+                               : AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, name.getBytes(encoding)), namespace, name,
+                               encoding);
+       }
+
+       /**
+        * Since no check is performed, the constructor is protected so that the object
+        * can only be built by the default methods of {@link TypedUuidFactory} (in this
+        * package) or by extending the class.
+        */
        protected NameUuid(UUID uuid, TypedUuid namespace, String name, Charset encoding) {
                super(uuid);
                assert namespace != null;
@@ -21,10 +43,6 @@ public class NameUuid extends UnkownNameUuid {
                this.encoding = encoding;
        }
 
-       public static long getSerialversionuid() {
-               return serialVersionUID;
-       }
-
        /** The namespace used to build this UUID. */
        public final TypedUuid getNamespace() {
                return namespace;
index ea71c483342770df6edbc7a31e9cc6f963e133e8..b65772e9b234dbcf565fb5e8217f5526076f92d9 100644 (file)
@@ -6,6 +6,7 @@ import java.util.UUID;
 public final class RandomUuid extends TypedUuid {
        private static final long serialVersionUID = APM.SERIAL;
 
+       /** Constructor based on a {@link UUID}. */
        public RandomUuid(UUID uuid) {
                super(uuid);
                if (uuid.version() != 4 && uuid.variant() != 2)
index 2f3a73fffc7c4cfd4af511557ef29cdfe0a18bb1..2276cd26831976344acb80e53b708aca902da854 100644 (file)
@@ -18,6 +18,7 @@ public class TimeUuid extends TypedUuid {
         */
        public final static Instant TIMESTAMP_ZERO = ZonedDateTime.of(1582, 10, 15, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
 
+       /** Constructor based on a {@link UUID}. */
        public TimeUuid(UUID uuid) {
                super(uuid);
                if (uuid.version() != 1 && uuid.variant() != 2)
index 55a67abef04dfde862d8b7e9de56e943cb73e950..2a0842fe6ef3679f5a82c9827b99441f5113d5b5 100644 (file)
@@ -4,8 +4,9 @@ import java.util.Objects;
 import java.util.UUID;
 
 /**
- * Base class for objects which are explicitly typed, based on the various UUID
- * versions. Such a derivation hierarchy still represents the {@link UUID}
+ * Base class for objects which are explicitly typed, based on the various
+ * variant 2 (RFC 4122) UUID versions (and variant 6 with {@link GUID}, for
+ * completion). Such a derivation hierarchy still represents the {@link UUID}
  * itself, not the objects, data or concept that it identifies. Just like
  * {@link UUID}s, {@link TypedUuid} should be used as identifier, not as base
  * class for complex objects. It should rather be seen as a framework to build
@@ -35,7 +36,9 @@ public abstract class TypedUuid extends UuidHolder {
 
        /**
         * Constructs a {@link TypedUuid} of the most appropriate subtype, based on this
-        * {@link UUID}.
+        * {@link UUID}. For name based UUIDs, it will return an opaque
+        * {@link BasicNameUuid}; {@link NameUuid} and {@link BinaryNameUuid} may be
+        * more useful.
         */
        public static TypedUuid of(UUID uuid) {
                Objects.requireNonNull(uuid, "UUID cannot be null");
@@ -47,14 +50,15 @@ public abstract class TypedUuid extends UuidHolder {
                                return new RandomUuid(uuid);
                        case 3:
                        case 5:
-                               return new UnkownNameUuid(uuid);
+                               return new BasicNameUuid(uuid);
                        default:
                                throw new IllegalArgumentException("UUIDs with version " + uuid.version() + " are not supported.");
                        }
-               } else if (uuid.variant() == 6) {// Microsoft
-                       throw new IllegalArgumentException("Microsoft UUIDs (aka. GUIDs) are not supported.");
+               } else if (uuid.variant() == 6) {// GUID
+                       return new GUID(uuid);
                } else {
                        throw new IllegalArgumentException("UUIDs with variant " + uuid.variant() + " are not supported.");
                }
        }
+
 }
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java
new file mode 100644 (file)
index 0000000..770e9b7
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.api.uuid;
+
+import java.nio.charset.Charset;
+
+/** An {@link UuidFactory} which also (trivially) produces {@link TypedUuid}. */
+public interface TypedUuidFactory extends UuidFactory {
+       /** Creates a {@link TimeUuid} (v1). */
+       default TimeUuid newTimeUuid() {
+               return new TimeUuid(timeUUID());
+       }
+
+       /** Creates an MD5 {@link NameUuid} (v3). */
+       default NameUuid newNameUuidV3(TypedUuid namespace, String name, Charset charset) {
+               return new NameUuid(nameUUIDv3(namespace.get(), name, charset), namespace, name, charset);
+       }
+
+       /** Creates a {@link RandomUuid}, using {@link #randomUUID()}. */
+       default RandomUuid newRandomUuid() {
+               return new RandomUuid(randomUUID());
+       }
+
+       /** Creates an SHA1 {@link NameUuid} (v5). */
+       default NameUuid newNameUuidV5(TypedUuid namespace, String name, Charset charset) {
+               return new NameUuid(nameUUIDv5(namespace.get(), name, charset), namespace, name, charset);
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/UnkownNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/UnkownNameUuid.java
deleted file mode 100644 (file)
index 623de91..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.argeo.api.uuid;
-
-import java.util.UUID;
-
-/** A name-based UUID (v3 or v5) whose construction values are not known. */
-public class UnkownNameUuid extends TypedUuid {
-       private static final long serialVersionUID = APM.SERIAL;
-
-       public UnkownNameUuid(UUID uuid) {
-               super(uuid);
-               if ((uuid.version() != 5 && uuid.version() != 3) || uuid.variant() != 2)
-                       throw new IllegalArgumentException("The provided UUID is not a name-based UUID.");
-       }
-
-       /**
-        * Always returns <code>true</true> since it is unknown from which values it was
-        * constructed..
-        */
-       @Override
-       public boolean isOpaque() {
-               return true;
-       }
-
-       /**
-        * Whether the hash of this name UUID was generated with SHA-1 (v5) or with MD5
-        * (v3).
-        */
-       public boolean isSha1() {
-               return uuid.version() == 5;
-       }
-}
index de0be36618558f4fa49c2a3cf09bfb222472d3fa..3ce27fce1ad02fcbe81967078cf72b1b11b2e0b2 100644 (file)
@@ -18,7 +18,7 @@ public class CmsUuidFactory extends ConcurrentUuidFactory {
 
        public CmsUuidFactory(byte[] nodeId) {
                super(nodeId);
-               assert newTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0];
+               assert createTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0];
        }
 
        public CmsUuidFactory() {