]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java
Add cr:path name
[lgpl/argeo-commons.git] / org.argeo.api.acr / src / org / argeo / api / acr / CrAttributeType.java
1 package org.argeo.api.acr;
2
3 import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
4
5 import java.net.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.Optional;
13 import java.util.UUID;
14
15 import javax.xml.namespace.NamespaceContext;
16 import javax.xml.namespace.QName;
17
18 /**
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>.
22 */
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, ArgeoNamespace.CR_NAMESPACE_URI, "uuid", new UuidFormatter()), //
32 QNAME(QName.class, W3C_XML_SCHEMA_NS_URI, "QName", new QNameFormatter()), //
33 ANY_URI(URI.class, W3C_XML_SCHEMA_NS_URI, "anyUri", new UriFormatter()), //
34 STRING(String.class, W3C_XML_SCHEMA_NS_URI, "string", new StringFormatter()), //
35 ;
36
37 private final Class<?> clss;
38 private final AttributeFormatter<?> formatter;
39
40 private final ContentName qName;
41
42 private <T> CrAttributeType(Class<T> clss, String namespaceUri, String localName, AttributeFormatter<T> formatter) {
43 this.clss = clss;
44 this.formatter = formatter;
45
46 qName = new ContentName(namespaceUri, localName, RuntimeNamespaceContext.getNamespaceContext());
47 }
48
49 public QName getQName() {
50 return qName;
51 }
52
53 public Class<?> getClss() {
54 return clss;
55 }
56
57 public AttributeFormatter<?> getFormatter() {
58 return formatter;
59 }
60
61 // @Override
62 // public String getDefaultPrefix() {
63 // if (equals(UUID))
64 // return CrName.CR_DEFAULT_PREFIX;
65 // else
66 // return "xs";
67 // }
68 //
69 // @Override
70 // public String getNamespaceURI() {
71 // if (equals(UUID))
72 // return CrName.CR_NAMESPACE_URI;
73 // else
74 // return XMLConstants.W3C_XML_SCHEMA_NS_URI;
75 // }
76
77 /** Default parsing procedure from a String to an object. */
78 public static Object parse(String str) {
79 return parse(RuntimeNamespaceContext.getNamespaceContext(), str);
80 }
81
82 /** Default parsing procedure from a String to an object. */
83 public static Object parse(NamespaceContext namespaceContext, String str) {
84 if (str == null)
85 throw new IllegalArgumentException("String cannot be null");
86 // order IS important
87 try {
88 if (str.length() == 4 || str.length() == 5)
89 return BOOLEAN.getFormatter().parse(namespaceContext, str);
90 } catch (IllegalArgumentException e) {
91 // silent
92 }
93 try {
94 return INTEGER.getFormatter().parse(namespaceContext, str);
95 } catch (IllegalArgumentException e) {
96 // silent
97 }
98 try {
99 return LONG.getFormatter().parse(namespaceContext, str);
100 } catch (IllegalArgumentException e) {
101 // silent
102 }
103 try {
104 return DOUBLE.getFormatter().parse(namespaceContext, str);
105 } catch (IllegalArgumentException e) {
106 // silent
107 }
108 try {
109 return DATE_TIME.getFormatter().parse(namespaceContext, str);
110 } catch (IllegalArgumentException e) {
111 // silent
112 }
113 try {
114 if (str.length() == 36)
115 return UUID.getFormatter().parse(namespaceContext, str);
116 } catch (IllegalArgumentException e) {
117 // silent
118 }
119
120 // CURIE
121 if (str.startsWith("[") && str.endsWith("]")) {
122 try {
123 if (str.indexOf(":") >= 0) {
124 QName qName = (QName) QNAME.getFormatter().parse(namespaceContext, str);
125 return (java.net.URI) ANY_URI.getFormatter().parse(qName.getNamespaceURI() + qName.getLocalPart());
126 }
127 } catch (IllegalArgumentException e) {
128 // silent
129 }
130 }
131
132 try {
133 if (str.indexOf(":") >= 0) {
134 QName qName = (QName) QNAME.getFormatter().parse(namespaceContext, str);
135 // note: this QName may not be valid
136 // note: CURIE should be explicitly defined with surrounding brackets
137 return qName;
138 }
139 } catch (IllegalArgumentException e) {
140 // silent
141 }
142 try {
143 java.net.URI uri = (java.net.URI) ANY_URI.getFormatter().parse(namespaceContext, str);
144 if (uri.getScheme() != null)
145 return uri;
146 String path = uri.getPath();
147 if (path.indexOf('/') >= 0)
148 return uri;
149 // if it is not clearly a path, we will consider it as a string
150 // because their is no way to distinguish between 'any_string'
151 // and 'any_file_name'.
152 // Note that providing ./any_file_name would result in an equivalent URI
153 } catch (IllegalArgumentException e) {
154 // silent
155 }
156
157 // TODO support QName as a type? It would require a NamespaceContext
158 // see https://www.oreilly.com/library/view/xml-schema/0596002521/re91.html
159
160 // default
161 return STRING.getFormatter().parse(namespaceContext, str);
162 }
163
164 /**
165 * Cast well know java types based on {@link Object#toString()} of the provided
166 * object.
167 *
168 */
169 public static <T> Optional<T> cast(Class<T> clss, Object value) {
170 return cast(RuntimeNamespaceContext.getNamespaceContext(), clss, value);
171 }
172
173 /**
174 * Cast well know java types based on {@link Object#toString()} of the provided
175 * object.
176 *
177 */
178 @SuppressWarnings("unchecked")
179 public static <T> Optional<T> cast(NamespaceContext namespaceContext, Class<T> clss, Object value) {
180 // if value is null, optional is empty
181 if (value == null)
182 return Optional.empty();
183
184 // if a default has been explicitly requested by passing Object.class
185 // we parse the related String
186 if (clss.isAssignableFrom(Object.class)) {
187 return Optional.of((T) parse(value.toString()));
188 }
189
190 // if value can be cast directly, let's do it
191 if (value.getClass().isAssignableFrom(clss)) {
192 return Optional.of(((T) value));
193 }
194
195 // let's cast between various numbers (possibly losing precision)
196 if (value instanceof Number number) {
197 if (Long.class.isAssignableFrom(clss))
198 return Optional.of((T) (Long) number.longValue());
199 else if (Integer.class.isAssignableFrom(clss))
200 return Optional.of((T) (Integer) number.intValue());
201 else if (Double.class.isAssignableFrom(clss))
202 return Optional.of((T) (Double) number.doubleValue());
203 }
204
205 // let's now try with the string representation
206 String strValue = value instanceof String ? (String) value : value.toString();
207
208 if (String.class.isAssignableFrom(clss)) {
209 return Optional.of((T) strValue);
210 }
211 if (java.util.UUID.class.isAssignableFrom(clss)) {
212 return Optional.of((T) java.util.UUID.fromString(strValue));
213 }
214 if (QName.class.isAssignableFrom(clss)) {
215 return Optional.of((T) NamespaceUtils.parsePrefixedName(namespaceContext, strValue));
216 }
217 // Numbers
218 else if (Long.class.isAssignableFrom(clss)) {
219 if (value instanceof Long)
220 return Optional.of((T) value);
221 return Optional.of((T) Long.valueOf(strValue));
222 } else if (Integer.class.isAssignableFrom(clss)) {
223 if (value instanceof Integer)
224 return Optional.of((T) value);
225 return Optional.of((T) Integer.valueOf(strValue));
226 } else if (Double.class.isAssignableFrom(clss)) {
227 if (value instanceof Double)
228 return Optional.of((T) value);
229 return Optional.of((T) Double.valueOf(strValue));
230 }
231
232 // let's now try to parse the string representation to a well-known type
233 Object parsedValue = parse(strValue);
234 if (parsedValue.getClass().isAssignableFrom(clss)) {
235 return Optional.of(((T) value));
236 }
237 throw new ClassCastException("Cannot convert " + value.getClass() + " to " + clss);
238 }
239
240 /** Utility to convert a data: URI to bytes. */
241 public static byte[] bytesFromDataURI(URI uri) {
242 if (!"data".equals(uri.getScheme()))
243 throw new IllegalArgumentException("URI must have 'data' as a scheme");
244 String schemeSpecificPart = uri.getSchemeSpecificPart();
245 int commaIndex = schemeSpecificPart.indexOf(',');
246 String prefix = schemeSpecificPart.substring(0, commaIndex);
247 List<String> info = Arrays.asList(prefix.split(";"));
248 if (!info.contains("base64"))
249 throw new IllegalArgumentException("URI must specify base64");
250
251 String base64Str = schemeSpecificPart.substring(commaIndex + 1);
252 return Base64.getDecoder().decode(base64Str);
253
254 }
255
256 /** Utility to convert bytes to a data: URI. */
257 public static URI bytesToDataURI(byte[] arr) {
258 String base64Str = Base64.getEncoder().encodeToString(arr);
259 try {
260 final String PREFIX = "data:application/octet-stream;base64,";
261 return new URI(PREFIX + base64Str);
262 } catch (URISyntaxException e) {
263 throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
264 }
265
266 }
267
268 static class BooleanFormatter implements AttributeFormatter<Boolean> {
269
270 /**
271 * @param str must be exactly equals to either 'true' or 'false' (different
272 * contract than {@link Boolean#parseBoolean(String)}.
273 */
274 @Override
275 public Boolean parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException {
276 if ("true".equals(str))
277 return Boolean.TRUE;
278 if ("false".equals(str))
279 return Boolean.FALSE;
280 throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
281 }
282 }
283
284 static class IntegerFormatter implements AttributeFormatter<Integer> {
285 @Override
286 public Integer parse(NamespaceContext namespaceContext, String str) throws NumberFormatException {
287 return Integer.parseInt(str);
288 }
289 }
290
291 static class LongFormatter implements AttributeFormatter<Long> {
292 @Override
293 public Long parse(NamespaceContext namespaceContext, String str) throws NumberFormatException {
294 return Long.parseLong(str);
295 }
296 }
297
298 static class DoubleFormatter implements AttributeFormatter<Double> {
299
300 @Override
301 public Double parse(NamespaceContext namespaceContext, String str) throws NumberFormatException {
302 return Double.parseDouble(str);
303 }
304 }
305
306 static class InstantFormatter implements AttributeFormatter<Instant> {
307
308 @Override
309 public Instant parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException {
310 try {
311 return Instant.parse(str);
312 } catch (DateTimeParseException e) {
313 throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
314 }
315 }
316 }
317
318 static class UuidFormatter implements AttributeFormatter<UUID> {
319
320 @Override
321 public UUID parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException {
322 return java.util.UUID.fromString(str);
323 }
324 }
325
326 static class UriFormatter implements AttributeFormatter<URI> {
327
328 @Override
329 public URI parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException {
330 try {
331 return new URI(str);
332 } catch (URISyntaxException e) {
333 throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
334 }
335 }
336
337 }
338
339 static class QNameFormatter implements AttributeFormatter<QName> {
340
341 @Override
342 public QName parse(NamespaceContext namespaceContext, String str) throws IllegalArgumentException {
343 return NamespaceUtils.parsePrefixedName(namespaceContext, str);
344 }
345
346 }
347
348 static class StringFormatter implements AttributeFormatter<String> {
349
350 @Override
351 public String parse(NamespaceContext namespaceContext, String str) {
352 return str;
353 }
354
355 @Override
356 public String format(String obj) {
357 return obj;
358 }
359
360 }
361
362 }