1 package org
.argeo
.api
.acr
;
3 import static javax
.xml
.XMLConstants
.W3C_XML_SCHEMA_NS_URI
;
6 import java
.net
.URISyntaxException
;
7 import java
.time
.Instant
;
8 import java
.time
.format
.DateTimeParseException
;
9 import java
.util
.Arrays
;
10 import java
.util
.Base64
;
11 import java
.util
.List
;
12 import java
.util
.Objects
;
13 import java
.util
.Optional
;
14 import java
.util
.UUID
;
16 import javax
.xml
.namespace
.QName
;
19 * Minimal standard attribute types that MUST be supported. All related classes
20 * belong to java.base and can be implicitly derived form a given
21 * <code>String</code>.
23 public enum CrAttributeType
{
24 BOOLEAN(Boolean
.class, W3C_XML_SCHEMA_NS_URI
, "boolean", new BooleanFormatter()), //
25 INTEGER(Integer
.class, W3C_XML_SCHEMA_NS_URI
, "integer", new IntegerFormatter()), //
26 LONG(Long
.class, W3C_XML_SCHEMA_NS_URI
, "long", new LongFormatter()), //
27 DOUBLE(Double
.class, W3C_XML_SCHEMA_NS_URI
, "double", new DoubleFormatter()), //
28 // we do not support short and float, like recent additions to Java
29 // (e.g. optional primitives)
30 DATE_TIME(Instant
.class, W3C_XML_SCHEMA_NS_URI
, "dateTime", new InstantFormatter()), //
31 UUID(UUID
.class, CrName
.CR_NAMESPACE_URI
, "uuid", new UuidFormatter()), //
32 ANY_URI(URI
.class, W3C_XML_SCHEMA_NS_URI
, "anyUri", new UriFormatter()), //
33 STRING(String
.class, W3C_XML_SCHEMA_NS_URI
, "string", new StringFormatter()), //
36 private final Class
<?
> clss
;
37 private final AttributeFormatter
<?
> formatter
;
39 private final ContentName qName
;
41 private <T
> CrAttributeType(Class
<T
> clss
, String namespaceUri
, String localName
, AttributeFormatter
<T
> formatter
) {
43 this.formatter
= formatter
;
45 qName
= new ContentName(namespaceUri
, localName
, RuntimeNamespaceContext
.getNamespaceContext());
48 public QName
getqName() {
52 public Class
<?
> getClss() {
56 public AttributeFormatter
<?
> getFormatter() {
61 // public String getDefaultPrefix() {
63 // return CrName.CR_DEFAULT_PREFIX;
69 // public String getNamespaceURI() {
71 // return CrName.CR_NAMESPACE_URI;
73 // return XMLConstants.W3C_XML_SCHEMA_NS_URI;
76 /** Default parsing procedure from a String to an object. */
77 public static Object
parse(String str
) {
79 throw new IllegalArgumentException("String cannot be null");
82 if (str
.length() == 4 || str
.length() == 5)
83 return BOOLEAN
.getFormatter().parse(str
);
84 } catch (IllegalArgumentException e
) {
88 return INTEGER
.getFormatter().parse(str
);
89 } catch (IllegalArgumentException e
) {
93 return LONG
.getFormatter().parse(str
);
94 } catch (IllegalArgumentException e
) {
98 return DOUBLE
.getFormatter().parse(str
);
99 } catch (IllegalArgumentException e
) {
103 return DATE_TIME
.getFormatter().parse(str
);
104 } catch (IllegalArgumentException e
) {
108 if (str
.length() == 36)
109 return UUID
.getFormatter().parse(str
);
110 } catch (IllegalArgumentException e
) {
114 java
.net
.URI uri
= (java
.net
.URI
) ANY_URI
.getFormatter().parse(str
);
115 if (uri
.getScheme() != null)
117 String path
= uri
.getPath();
118 if (path
.indexOf('/') >= 0)
120 // if it is not clearly a path, we will consider it as a string
121 // because their is no way to distinguish between 'any_string'
122 // and 'any_file_name'.
123 // Note that providing ./any_file_name would result in an equivalent URI
124 } catch (IllegalArgumentException e
) {
128 // TODO support QName as a type? It would require a NamespaceContext
129 // see https://www.oreilly.com/library/view/xml-schema/0596002521/re91.html
132 return STRING
.getFormatter().parse(str
);
136 * Cast well know java types based on {@link Object#toString()} of the provided
140 @SuppressWarnings("unchecked")
141 public static <T
> Optional
<T
> cast(Class
<T
> clss
, Object value
) {
142 // TODO Or should we?
143 Objects
.requireNonNull(value
, "Cannot cast a null value");
144 if (String
.class.isAssignableFrom(clss
)) {
145 return Optional
.of((T
) value
.toString());
148 else if (Long
.class.isAssignableFrom(clss
)) {
149 if (value
instanceof Long
)
150 return Optional
.of((T
) value
);
151 return Optional
.of((T
) Long
.valueOf(value
.toString()));
152 } else if (Integer
.class.isAssignableFrom(clss
)) {
153 if (value
instanceof Integer
)
154 return Optional
.of((T
) value
);
155 return Optional
.of((T
) Integer
.valueOf(value
.toString()));
156 } else if (Double
.class.isAssignableFrom(clss
)) {
157 if (value
instanceof Double
)
158 return Optional
.of((T
) value
);
159 return Optional
.of((T
) Double
.valueOf(value
.toString()));
162 // else if (Number.class.isAssignableFrom(clss)) {
163 // if (value instanceof Number)
164 // return Optional.of((T) value);
165 // return Optional.of((T) Number.valueOf(value.toString()));
167 return Optional
.empty();
170 /** Utility to convert a data: URI to bytes. */
171 public static byte[] bytesFromDataURI(URI uri
) {
172 if (!"data".equals(uri
.getScheme()))
173 throw new IllegalArgumentException("URI must have 'data' as a scheme");
174 String schemeSpecificPart
= uri
.getSchemeSpecificPart();
175 int commaIndex
= schemeSpecificPart
.indexOf(',');
176 String prefix
= schemeSpecificPart
.substring(0, commaIndex
);
177 List
<String
> info
= Arrays
.asList(prefix
.split(";"));
178 if (!info
.contains("base64"))
179 throw new IllegalArgumentException("URI must specify base64");
181 String base64Str
= schemeSpecificPart
.substring(commaIndex
+ 1);
182 return Base64
.getDecoder().decode(base64Str
);
186 /** Utility to convert bytes to a data: URI. */
187 public static URI
bytesToDataURI(byte[] arr
) {
188 String base64Str
= Base64
.getEncoder().encodeToString(arr
);
190 final String PREFIX
= "data:application/octet-stream;base64,";
191 return new URI(PREFIX
+ base64Str
);
192 } catch (URISyntaxException e
) {
193 throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e
);
198 static class BooleanFormatter
implements AttributeFormatter
<Boolean
> {
201 * @param str must be exactly equals to either 'true' or 'false' (different
202 * contract than {@link Boolean#parseBoolean(String)}.
205 public Boolean
parse(String str
) throws IllegalArgumentException
{
206 if ("true".equals(str
))
208 if ("false".equals(str
))
209 return Boolean
.FALSE
;
210 throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str
);
214 static class IntegerFormatter
implements AttributeFormatter
<Integer
> {
216 public Integer
parse(String str
) throws NumberFormatException
{
217 return Integer
.parseInt(str
);
221 static class LongFormatter
implements AttributeFormatter
<Long
> {
223 public Long
parse(String str
) throws NumberFormatException
{
224 return Long
.parseLong(str
);
228 static class DoubleFormatter
implements AttributeFormatter
<Double
> {
231 public Double
parse(String str
) throws NumberFormatException
{
232 return Double
.parseDouble(str
);
236 static class InstantFormatter
implements AttributeFormatter
<Instant
> {
239 public Instant
parse(String str
) throws IllegalArgumentException
{
241 return Instant
.parse(str
);
242 } catch (DateTimeParseException e
) {
243 throw new IllegalArgumentException("Cannot parse '" + str
+ "' as an instant", e
);
248 static class UuidFormatter
implements AttributeFormatter
<UUID
> {
251 public UUID
parse(String str
) throws IllegalArgumentException
{
252 return java
.util
.UUID
.fromString(str
);
256 static class UriFormatter
implements AttributeFormatter
<URI
> {
259 public URI
parse(String str
) throws IllegalArgumentException
{
262 } catch (URISyntaxException e
) {
263 throw new IllegalArgumentException("Cannot parse " + str
+ " as an URI.", e
);
269 static class StringFormatter
implements AttributeFormatter
<String
> {
272 public String
parse(String str
) {
277 public String
format(String obj
) {