--- /dev/null
+package org.argeo.api.gcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * An XML-style namespace with a related UUID v3.
+ *
+ * @see https://www.w3.org/TR/xml-names/
+ */
+public class ContentNamespace {
+ private final UUID uuid;
+ private final URI uri;
+
+ public ContentNamespace(String uri) {
+ try {
+ this.uri = new URI(uri);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot interpret " + uri + " as URI", e);
+ }
+ this.uuid = namespaceUuid(this.uri);
+ }
+
+ public ContentNamespace(URI uri) {
+ this.uri = uri;
+ this.uuid = namespaceUuid(uri);
+ }
+
+ ContentNamespace(URI uri, UUID uuid) {
+ this.uri = uri;
+ assert uuid.equals(namespaceUuid(uri));
+ this.uuid = uuid;
+ }
+
+ /** Empty namespace */
+ private ContentNamespace() {
+ try {
+ this.uri = new URI("");
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot create empty URI");
+ }
+ this.uuid = NIL_UUID;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+ public UUID nameUuid(String name) {
+ return nameUuid(getUuid(), name);
+ }
+
+
+ public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ public final static ContentNamespace EMPTY_NS = new ContentNamespace();
+
+ /**
+ * The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS
+ * namespace, to be used as a base for the namespaces.
+ *
+ * @see https://www.w3.org/TR/xml-names/#ns-decl
+ */
+ // uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/
+ // NOTE : must be declared before default namespaces
+ public final static UUID XMLNS_UUID = UUID.fromString("4b352aad-ba1c-3139-b9d3-41e5816f6088");
+ public final static ContentNamespace CR_NS = new ContentNamespace("http://argeo.org/ns/cr");
+
+
+ public static UUID namespaceUuid(URI namespaceUri) {
+ Objects.requireNonNull(namespaceUri, "Namespace URI cannot be null");
+ return nameUUIDv3(XMLNS_UUID, namespaceUri.toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static UUID nameUuid(UUID namespace, String name) {
+ Objects.requireNonNull(namespace, "Namespace cannot be null");
+ Objects.requireNonNull(namespace, "Name cannot be null");
+ return nameUUIDv3(namespace, name.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /*
+ * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+ */
+ private static UUID nameUUIDv3(UUID namespace, byte[] name) {
+ byte[] arr = new byte[name.length + 16];
+ copyUuidBytes(namespace, arr, 0);
+ System.arraycopy(name, 0, arr, 16, name.length);
+ return UUID.nameUUIDFromBytes(arr);
+ }
+
+ private static void copyUuidBytes(UUID uuid, byte[] arr, int offset) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+ assert arr.length >= 16 + offset;
+ for (int i = offset; i < 8 + offset; i++)
+ arr[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
+ for (int i = 8 + offset; i < 16 + offset; i++)
+ arr[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
+ }
+}