Introduce namespace support
[lgpl/argeo-commons.git] / org.argeo.api / src / org / argeo / api / gcr / ContentName.java
index ddc80c5b621aae1f7766b3d63d2062a1490a671f..5acd53a00a8b506cb4c5e5b9f0a0867500784775 100644 (file)
 package org.argeo.api.gcr;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
 import java.util.UUID;
 
-public interface ContentName {
-       UUID getUuid();
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+/**
+ * A {@link QName} which MUST have prefix and whose {@link #toString()} method
+ * returns the prefixed form (prefix:localPart).
+ */
+public class ContentName extends QName {
+       private static final long serialVersionUID = 5722920985400306100L;
+       public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+       /**
+        * 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");
+       // uuidgen --md5 --namespace 4b352aad-ba1c-3139-b9d3-41e5816f6088 --name ""
+       public final static UUID NULL_NS_UUID = UUID.fromString("f07726e3-99c8-3178-b758-a86ed41f300d");
+
+       private final static Map<String, UUID> namespaceUuids = Collections.synchronizedMap(new TreeMap<>());
+       private final static Map<String, UUID> nameUuids = Collections.synchronizedMap(new TreeMap<>());
+
+       static {
+               assert NULL_NS_UUID.equals(nameUUIDv3(XMLNS_UUID, XMLConstants.NULL_NS_URI.getBytes(UTF_8)));
+       }
+
+//     private final UUID uuid;
+
+       public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
+               this(namespaceURI, localPart, nsContext.getPrefix(namespaceURI));
+       }
+
+       protected ContentName(String namespaceURI, String localPart, String prefix) {
+               super(namespaceURI, localPart, prefix);
+               if (prefix == null)
+                       throw new IllegalArgumentException("Prefix annot be null");
+       }
+
+       public ContentName(String localPart) {
+               this(XMLConstants.NULL_NS_URI, localPart, XMLConstants.DEFAULT_NS_PREFIX);
+       }
+
+       public ContentName(QName qName, NamespaceContext nsContext) {
+               this(qName.getNamespaceURI(), qName.getLocalPart(), nsContext);
+       }
+
+       public String toQNameString() {
+               return super.toString();
+       }
+
+       public String toPrefixedString() {
+               return toPrefixedString(this);
+       }
+
+       /*
+        * OBJECT METHOS
+        */
+
+       @Override
+       public String toString() {
+               return toPrefixedString();
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new ContentName(getNamespaceURI(), getLocalPart(), getPrefix());
+       }
+
+       public static String toPrefixedString(QName name) {
+               String prefix = name.getPrefix();
+               assert prefix != null;
+               return "".equals(prefix) ? name.getLocalPart() : prefix + ":" + name.getLocalPart();
+       }
+//     ContentNamespace getNamespace();
+//
+//     String getName();
+
+       public static UUID namespaceUuid(String namespaceURI) {
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return NULL_NS_UUID;
+               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+               synchronized (namespaceUuids) {
+                       UUID namespaceUuid = namespaceUuids.get(namespaceURI);
+                       if (namespaceUuid == null) {
+                               namespaceUuid = nameUUIDv3(ContentName.XMLNS_UUID,
+                                               namespaceURI.toString().getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(namespaceURI, namespaceUuid);
+                       }
+                       return namespaceUuid;
+               }
+       }
+
+       public static UUID nameUuid(String namespaceURI, QName name) {
+               return nameUuid(name.getNamespaceURI(), name.getLocalPart());
+       }
 
-       ContentNamespace getNamespace();
+       public static UUID nameUuid(String namespaceURI, String name) {
+               Objects.requireNonNull(namespaceURI, "Namespace cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
+               synchronized (nameUuids) {
+                       String key = XMLConstants.NULL_NS_URI.equals(namespaceURI) ? name : "{" + namespaceURI + "}" + name;
+                       UUID nameUuid = nameUuids.get(key);
+                       if (nameUuid == null) {
+                               UUID namespaceUuid = namespaceUuid(namespaceURI);
+                               nameUuid = nameUUIDv3(namespaceUuid, name.getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(key, nameUuid);
+                       }
+                       return nameUuid;
+               }
+       }
+
+       /*
+        * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+        */
+       static UUID nameUUIDv3(UUID namespace, byte[] name) {
+               byte[] arr = new byte[name.length + 16];
+               ContentName.copyUuidBytes(namespace, arr, 0);
+               System.arraycopy(name, 0, arr, 16, name.length);
+               return UUID.nameUUIDFromBytes(arr);
+       }
+
+       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);
+       }
 
-       String getName();
+       /*
+        * UTILITIES
+        */
 
-       static boolean contains(ContentName[] classes, ContentName name) {
-               for (ContentName clss : classes) {
-                       if (clss.getUuid().equals(name.getUuid()))
+       public static boolean contains(QName[] classes, QName name) {
+               for (QName clss : classes) {
+                       if (clss.equals(name))
                                return true;
                }
                return false;