X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.api.acr%2Fsrc%2Forg%2Fargeo%2Fapi%2Facr%2FCrAttributeType.java;h=3e0dddee4c55ed6cf6f6f81e836d8941dca3fd52;hb=5724ab347ddfba8f2b21cdcc2fa0b8e1e2b4e527;hp=ffa28af0aca580a6d358030d9f1e4c038f1aa179;hpb=7d2a002f5dcfe8a8c7b29803b70d4b1aff265ed1;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java index ffa28af0a..3e0dddee4 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java @@ -1,37 +1,53 @@ package org.argeo.api.acr; +import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; + import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Optional; import java.util.UUID; -import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; /** * Minimal standard attribute types that MUST be supported. All related classes * belong to java.base and can be implicitly derived form a given - * String. + * String. */ -public enum CrAttributeType implements ContentNameSupplier { - BOOLEAN(Boolean.class, new BooleanFormatter()), // - INTEGER(Integer.class, new IntegerFormatter()), // - LONG(Long.class, new LongFormatter()), // - DOUBLE(Double.class, new DoubleFormatter()), // +public enum CrAttributeType { + BOOLEAN(Boolean.class, W3C_XML_SCHEMA_NS_URI, "boolean", new BooleanFormatter()), // + INTEGER(Integer.class, W3C_XML_SCHEMA_NS_URI, "integer", new IntegerFormatter()), // + LONG(Long.class, W3C_XML_SCHEMA_NS_URI, "long", new LongFormatter()), // + DOUBLE(Double.class, W3C_XML_SCHEMA_NS_URI, "double", new DoubleFormatter()), // // we do not support short and float, like recent additions to Java // (e.g. optional primitives) - DATE_TIME(Instant.class, new InstantFormatter()), // - UUID(UUID.class, new UuidFormatter()), // - ANY_URI(URI.class, new UriFormatter()), // - STRING(String.class, new StringFormatter()), // + DATE_TIME(Instant.class, W3C_XML_SCHEMA_NS_URI, "dateTime", new InstantFormatter()), // + UUID(UUID.class, ArgeoNamespace.CR_NAMESPACE_URI, "uuid", new UuidFormatter()), // + QNAME(QName.class, W3C_XML_SCHEMA_NS_URI, "QName", new QNameFormatter()), // + ANY_URI(URI.class, W3C_XML_SCHEMA_NS_URI, "anyUri", new UriFormatter()), // + STRING(String.class, W3C_XML_SCHEMA_NS_URI, "string", new StringFormatter()), // ; private final Class clss; private final AttributeFormatter formatter; - private CrAttributeType(Class clss, AttributeFormatter formatter) { + private final ContentName qName; + + private CrAttributeType(Class clss, String namespaceUri, String localName, AttributeFormatter formatter) { this.clss = clss; this.formatter = formatter; + + qName = new ContentName(namespaceUri, localName, RuntimeNamespaceContext.getNamespaceContext()); + } + + public QName getQName() { + return qName; } public Class getClss() { @@ -42,60 +58,89 @@ public enum CrAttributeType implements ContentNameSupplier { return formatter; } - @Override - public String getDefaultPrefix() { - if (equals(UUID)) - return CrName.CR_DEFAULT_PREFIX; - else - return "xs"; - } +// @Override +// public String getDefaultPrefix() { +// if (equals(UUID)) +// return CrName.CR_DEFAULT_PREFIX; +// else +// return "xs"; +// } +// +// @Override +// public String getNamespaceURI() { +// if (equals(UUID)) +// return CrName.CR_NAMESPACE_URI; +// else +// return XMLConstants.W3C_XML_SCHEMA_NS_URI; +// } - @Override - public String getNamespaceURI() { - if (equals(UUID)) - return CrName.CR_NAMESPACE_URI; - else - return XMLConstants.W3C_XML_SCHEMA_NS_URI; + /** Default parsing procedure from a String to an object. */ + public static Object parse(String str) { + return parse(RuntimeNamespaceContext.getNamespaceContext(), str); } - public static Object parse(String str) { + /** Default parsing procedure from a String to an object. */ + public static Object parse(NamespaceContext namespaceContext, String str) { if (str == null) throw new IllegalArgumentException("String cannot be null"); // order IS important try { if (str.length() == 4 || str.length() == 5) - return BOOLEAN.getFormatter().parse(str); + return BOOLEAN.getFormatter().parse(namespaceContext, str); } catch (IllegalArgumentException e) { // silent } try { - return INTEGER.getFormatter().parse(str); + return INTEGER.getFormatter().parse(namespaceContext, str); } catch (IllegalArgumentException e) { // silent } try { - return LONG.getFormatter().parse(str); + return LONG.getFormatter().parse(namespaceContext, str); } catch (IllegalArgumentException e) { // silent } try { - return DOUBLE.getFormatter().parse(str); + return DOUBLE.getFormatter().parse(namespaceContext, str); } catch (IllegalArgumentException e) { // silent } try { - return DATE_TIME.getFormatter().parse(str); + return DATE_TIME.getFormatter().parse(namespaceContext, str); } catch (IllegalArgumentException e) { // silent } try { if (str.length() == 36) - return UUID.getFormatter().parse(str); + return UUID.getFormatter().parse(namespaceContext, str); + } catch (IllegalArgumentException e) { + // silent + } + + // CURIE + if (str.startsWith("[") && str.endsWith("]")) { + try { + if (str.indexOf(":") >= 0) { + QName qName = (QName) QNAME.getFormatter().parse(namespaceContext, str); + return (java.net.URI) ANY_URI.getFormatter().parse(qName.getNamespaceURI() + qName.getLocalPart()); + } + } catch (IllegalArgumentException e) { + // silent + } + } + + try { + if (str.indexOf(":") >= 0) { + QName qName = (QName) QNAME.getFormatter().parse(namespaceContext, str); + // note: this QName may not be valid + // note: CURIE should be explicitly defined with surrounding brackets + return qName; + } } catch (IllegalArgumentException e) { // silent } try { - java.net.URI uri = (java.net.URI) ANY_URI.getFormatter().parse(str); + java.net.URI uri = (java.net.URI) ANY_URI.getFormatter().parse(namespaceContext, str); if (uri.getScheme() != null) return uri; String path = uri.getPath(); @@ -109,8 +154,112 @@ public enum CrAttributeType implements ContentNameSupplier { // silent } + // TODO support QName as a type? It would require a NamespaceContext + // see https://www.oreilly.com/library/view/xml-schema/0596002521/re91.html + // default - return STRING.getFormatter().parse(str); + return STRING.getFormatter().parse(namespaceContext, str); + } + + /** + * Cast well know java types based on {@link Object#toString()} of the provided + * object. + * + */ + public static Optional cast(Class clss, Object value) { + return cast(RuntimeNamespaceContext.getNamespaceContext(), clss, value); + } + + /** + * Cast well know java types based on {@link Object#toString()} of the provided + * object. + * + */ + @SuppressWarnings("unchecked") + public static Optional cast(NamespaceContext namespaceContext, Class clss, Object value) { + // if value is null, optional is empty + if (value == null) + return Optional.empty(); + + // if a default has been explicitly requested by passing Object.class + // we parse the related String + if (clss.isAssignableFrom(Object.class)) { + return Optional.of((T) parse(value.toString())); + } + + // if value can be cast directly, let's do it + if (value.getClass().isAssignableFrom(clss)) { + return Optional.of(((T) value)); + } + + // let's cast between various numbers (possibly losing precision) + if (value instanceof Number number) { + if (Long.class.isAssignableFrom(clss)) + return Optional.of((T) (Long) number.longValue()); + else if (Integer.class.isAssignableFrom(clss)) + return Optional.of((T) (Integer) number.intValue()); + else if (Double.class.isAssignableFrom(clss)) + return Optional.of((T) (Double) number.doubleValue()); + } + + // let's now try with the string representation + String strValue = value instanceof String ? (String) value : value.toString(); + + if (String.class.isAssignableFrom(clss)) { + return Optional.of((T) strValue); + } + if (QName.class.isAssignableFrom(clss)) { + return Optional.of((T) NamespaceUtils.parsePrefixedName(namespaceContext, strValue)); + } + // Numbers + else if (Long.class.isAssignableFrom(clss)) { + if (value instanceof Long) + return Optional.of((T) value); + return Optional.of((T) Long.valueOf(strValue)); + } else if (Integer.class.isAssignableFrom(clss)) { + if (value instanceof Integer) + return Optional.of((T) value); + return Optional.of((T) Integer.valueOf(strValue)); + } else if (Double.class.isAssignableFrom(clss)) { + if (value instanceof Double) + return Optional.of((T) value); + return Optional.of((T) Double.valueOf(strValue)); + } + + // let's now try to parse the string representation to a well-known type + Object parsedValue = parse(strValue); + if (parsedValue.getClass().isAssignableFrom(clss)) { + return Optional.of(((T) value)); + } + throw new ClassCastException("Cannot convert " + value.getClass() + " to " + clss); + } + + /** Utility to convert a data: URI to bytes. */ + public static byte[] bytesFromDataURI(URI uri) { + if (!"data".equals(uri.getScheme())) + throw new IllegalArgumentException("URI must have 'data' as a scheme"); + String schemeSpecificPart = uri.getSchemeSpecificPart(); + int commaIndex = schemeSpecificPart.indexOf(','); + String prefix = schemeSpecificPart.substring(0, commaIndex); + List info = Arrays.asList(prefix.split(";")); + if (!info.contains("base64")) + throw new IllegalArgumentException("URI must specify base64"); + + String base64Str = schemeSpecificPart.substring(commaIndex + 1); + return Base64.getDecoder().decode(base64Str); + + } + + /** Utility to convert bytes to a data: URI. */ + public static URI bytesToDataURI(byte[] arr) { + String base64Str = Base64.getEncoder().encodeToString(arr); + try { + final String PREFIX = "data:application/octet-stream;base64,"; + return new URI(PREFIX + base64Str); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e); + } + } static class BooleanFormatter implements AttributeFormatter { @@ -120,7 +269,7 @@ public enum CrAttributeType implements ContentNameSupplier { * contract than {@link Boolean#parseBoolean(String)}. */ @Override - public Boolean parse(String str) throws IllegalArgumentException { + public Boolean parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException { if ("true".equals(str)) return Boolean.TRUE; if ("false".equals(str)) @@ -131,14 +280,14 @@ public enum CrAttributeType implements ContentNameSupplier { static class IntegerFormatter implements AttributeFormatter { @Override - public Integer parse(String str) throws NumberFormatException { + public Integer parse(NamespaceContext namespaceContext, String str) throws NumberFormatException { return Integer.parseInt(str); } } static class LongFormatter implements AttributeFormatter { @Override - public Long parse(String str) throws NumberFormatException { + public Long parse(NamespaceContext namespaceContext, String str) throws NumberFormatException { return Long.parseLong(str); } } @@ -146,7 +295,7 @@ public enum CrAttributeType implements ContentNameSupplier { static class DoubleFormatter implements AttributeFormatter { @Override - public Double parse(String str) throws NumberFormatException { + public Double parse(NamespaceContext namespaceContext, String str) throws NumberFormatException { return Double.parseDouble(str); } } @@ -154,7 +303,7 @@ public enum CrAttributeType implements ContentNameSupplier { static class InstantFormatter implements AttributeFormatter { @Override - public Instant parse(String str) throws IllegalArgumentException { + public Instant parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException { try { return Instant.parse(str); } catch (DateTimeParseException e) { @@ -166,7 +315,7 @@ public enum CrAttributeType implements ContentNameSupplier { static class UuidFormatter implements AttributeFormatter { @Override - public UUID parse(String str) throws IllegalArgumentException { + public UUID parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException { return java.util.UUID.fromString(str); } } @@ -174,7 +323,7 @@ public enum CrAttributeType implements ContentNameSupplier { static class UriFormatter implements AttributeFormatter { @Override - public URI parse(String str) throws IllegalArgumentException { + public URI parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException { try { return new URI(str); } catch (URISyntaxException e) { @@ -184,10 +333,19 @@ public enum CrAttributeType implements ContentNameSupplier { } + static class QNameFormatter implements AttributeFormatter { + + @Override + public QName parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException { + return NamespaceUtils.parsePrefixedName(namespaceContext, str); + } + + } + static class StringFormatter implements AttributeFormatter { @Override - public String parse(String str) { + public String parse(NamespaceContext namespaceContext, String str) { return str; }