1
2
3
4
5
6
7
8
9
10
11
12
13
14 package eu.fbk.rdfpro.util;
15
16 import java.math.BigDecimal;
17 import java.math.BigInteger;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Objects;
21
22 import javax.annotation.Nullable;
23 import javax.xml.datatype.XMLGregorianCalendar;
24
25 import org.openrdf.model.BNode;
26 import org.openrdf.model.Literal;
27 import org.openrdf.model.Resource;
28 import org.openrdf.model.Statement;
29 import org.openrdf.model.URI;
30 import org.openrdf.model.Value;
31 import org.openrdf.model.datatypes.XMLDatatypeUtil;
32 import org.openrdf.model.impl.ContextStatementImpl;
33 import org.openrdf.model.impl.StatementImpl;
34 import org.openrdf.model.impl.ValueFactoryBase;
35 import org.openrdf.model.util.URIUtil;
36 import org.openrdf.model.vocabulary.OWL;
37 import org.openrdf.model.vocabulary.RDF;
38 import org.openrdf.model.vocabulary.RDFS;
39 import org.openrdf.model.vocabulary.XMLSchema;
40
41 final class HashValueFactory extends ValueFactoryBase {
42
43 public static final HashValueFactory INSTANCE = new HashValueFactory();
44
45 private final Map<String, URI> w3cURIs;
46
47 private HashValueFactory() {
48 this.w3cURIs = new HashMap<>(1024);
49 for (final URI uri : new URI[] { XMLSchema.DECIMAL, XMLSchema.INTEGER,
50 XMLSchema.NON_POSITIVE_INTEGER, XMLSchema.NEGATIVE_INTEGER,
51 XMLSchema.NON_NEGATIVE_INTEGER, XMLSchema.POSITIVE_INTEGER, XMLSchema.LONG,
52 XMLSchema.INT, XMLSchema.SHORT, XMLSchema.BYTE, XMLSchema.UNSIGNED_LONG,
53 XMLSchema.UNSIGNED_INT, XMLSchema.UNSIGNED_SHORT, XMLSchema.UNSIGNED_BYTE,
54 XMLSchema.DOUBLE, XMLSchema.FLOAT, XMLSchema.BOOLEAN, XMLSchema.DATETIME,
55 XMLSchema.DATE, XMLSchema.TIME, XMLSchema.GYEARMONTH, XMLSchema.GMONTHDAY,
56 XMLSchema.GYEAR, XMLSchema.GMONTH, XMLSchema.GDAY, XMLSchema.DURATION,
57 XMLSchema.DAYTIMEDURATION, XMLSchema.STRING, XMLSchema.BASE64BINARY,
58 XMLSchema.HEXBINARY, XMLSchema.ANYURI, XMLSchema.QNAME, XMLSchema.NOTATION,
59 XMLSchema.NORMALIZEDSTRING, XMLSchema.TOKEN, XMLSchema.LANGUAGE,
60 XMLSchema.NMTOKEN, XMLSchema.NMTOKENS, XMLSchema.NAME, XMLSchema.NCNAME,
61 XMLSchema.ID, XMLSchema.IDREF, XMLSchema.IDREFS, XMLSchema.ENTITY,
62 XMLSchema.ENTITIES, RDF.TYPE, RDF.PROPERTY, RDF.XMLLITERAL, RDF.SUBJECT,
63 RDF.PREDICATE, RDF.OBJECT, RDF.STATEMENT, RDF.BAG, RDF.ALT, RDF.SEQ, RDF.VALUE,
64 RDF.LI, RDF.LIST, RDF.FIRST, RDF.REST, RDF.NIL, RDF.LANGSTRING, RDF.HTML,
65 RDFS.RESOURCE, RDFS.LITERAL, RDFS.CLASS, RDFS.SUBCLASSOF, RDFS.SUBPROPERTYOF,
66 RDFS.DOMAIN, RDFS.RANGE, RDFS.COMMENT, RDFS.LABEL, RDFS.DATATYPE, RDFS.CONTAINER,
67 RDFS.MEMBER, RDFS.ISDEFINEDBY, RDFS.SEEALSO, RDFS.CONTAINERMEMBERSHIPPROPERTY,
68 OWL.CLASS, OWL.INDIVIDUAL, OWL.THING, OWL.NOTHING, OWL.EQUIVALENTCLASS,
69 OWL.EQUIVALENTPROPERTY, OWL.SAMEAS, OWL.DIFFERENTFROM, OWL.ALLDIFFERENT,
70 OWL.DISTINCTMEMBERS, OWL.OBJECTPROPERTY, OWL.DATATYPEPROPERTY, OWL.INVERSEOF,
71 OWL.TRANSITIVEPROPERTY, OWL.SYMMETRICPROPERTY, OWL.FUNCTIONALPROPERTY,
72 OWL.INVERSEFUNCTIONALPROPERTY, OWL.RESTRICTION, OWL.ONPROPERTY, OWL.ALLVALUESFROM,
73 OWL.SOMEVALUESFROM, OWL.MINCARDINALITY, OWL.MAXCARDINALITY, OWL.CARDINALITY,
74 OWL.ONTOLOGY, OWL.IMPORTS, OWL.INTERSECTIONOF, OWL.VERSIONINFO, OWL.VERSIONIRI,
75 OWL.PRIORVERSION, OWL.BACKWARDCOMPATIBLEWITH, OWL.INCOMPATIBLEWITH,
76 OWL.DEPRECATEDCLASS, OWL.DEPRECATEDPROPERTY, OWL.ANNOTATIONPROPERTY,
77 OWL.ONTOLOGYPROPERTY, OWL.ONEOF, OWL.HASVALUE, OWL.DISJOINTWITH, OWL.UNIONOF,
78 OWL.COMPLEMENTOF }) {
79 final HashURI h = new HashURI(uri.stringValue());
80 h.initHash();
81 this.w3cURIs.put(uri.stringValue(), h);
82 }
83 }
84
85 private boolean isPossibleW3CURI(final String uri) {
86 return uri.length() > 33 && uri.charAt(12) == '3';
87 }
88
89 @Override
90 public URI createURI(final String uri) {
91 if (isPossibleW3CURI(uri)) {
92 final URI u = this.w3cURIs.get(uri);
93 if (u != null) {
94 return u;
95 }
96 }
97 return new HashURI(uri);
98 }
99
100 @Override
101 public URI createURI(final String namespace, final String localName) {
102 final String uri = namespace + localName;
103 if (isPossibleW3CURI(uri)) {
104 final URI u = this.w3cURIs.get(uri);
105 if (u != null) {
106 return u;
107 }
108 }
109 if (URIUtil.isCorrectURISplit(namespace, localName)) {
110 return new HashURI(uri, namespace, localName);
111 } else {
112 return new HashURI(uri);
113 }
114 }
115
116 @Override
117 public BNode createBNode(final String id) {
118 return new HashBNode(id);
119 }
120
121 @Override
122 public Literal createLiteral(final String label) {
123 return new HashLiteral(label, null, null);
124 }
125
126 @Override
127 public Literal createLiteral(final String label, final String language) {
128 return new HashLiteral(label, language.intern(), null);
129 }
130
131 @Override
132 public Literal createLiteral(final String label, final URI datatype) {
133 return new HashLiteral(label, null, datatype);
134 }
135
136 @Override
137 public Statement createStatement(final Resource subj, final URI pred, final Value obj) {
138 return new StatementImpl(subj, pred, obj);
139 }
140
141 @Override
142 public Statement createStatement(final Resource subj, final URI pred, final Value obj,
143 final Resource ctx) {
144 return ctx == null ? new StatementImpl(subj, pred, obj)
145 : new ContextStatementImpl(subj, pred, obj, ctx);
146 }
147
148 @SuppressWarnings("unchecked")
149 @Nullable
150 public static <T extends Value> T normalize(@Nullable final T value) {
151 if (value instanceof HashValue) {
152 return value;
153 } else if (value instanceof URI) {
154 return (T) new HashURI(value.stringValue());
155 } else if (value instanceof BNode) {
156 return (T) new HashBNode(((BNode) value).getID());
157 } else if (value instanceof Literal) {
158 final Literal literal = (Literal) value;
159 return (T) new HashLiteral(literal.getLabel(), literal.getLanguage(),
160 literal.getDatatype());
161 } else {
162 return null;
163 }
164 }
165
166 private static abstract class HashValue implements Value, Hashable {
167
168 private static final long serialVersionUID = 1L;
169
170 transient long hashLo;
171
172 transient long hashHi;
173
174 transient int hashCode;
175
176 @Override
177 public final Hash getHash() {
178 Hash hash;
179 if (this.hashLo != 0L) {
180 hash = Hash.fromLongs(this.hashHi, this.hashLo);
181 } else {
182 hash = Statements.computeHash(this);
183 this.hashHi = hash.getHigh();
184 this.hashLo = hash.getLow();
185 }
186 return hash;
187 }
188
189 final boolean hasHash() {
190 return this.hashLo != 0L;
191 }
192
193 final void initHash() {
194 if (this.hashLo == 0L) {
195 final Hash hash = Statements.computeHash(this);
196 this.hashLo = hash.getLow();
197 this.hashHi = hash.getHigh();
198 }
199 }
200
201 final boolean sameHash(final HashValue other) {
202 initHash();
203 other.initHash();
204 return this.hashLo == other.hashLo && this.hashHi == other.hashHi;
205 }
206
207 }
208
209 private static abstract class HashResource extends HashValue implements Resource {
210
211 private static final long serialVersionUID = 1L;
212
213 }
214
215 private static final class HashLiteral extends HashValue implements Literal {
216
217 private static final long serialVersionUID = 1L;
218
219 private final String label;
220
221 @Nullable
222 private final Object languageOrDatatype;
223
224 HashLiteral(final String label, final String language, final URI datatype) {
225 this.label = label;
226 this.languageOrDatatype = language != null ? language : datatype;
227 }
228
229 @Override
230 public String getLabel() {
231 return this.label;
232 }
233
234 @Override
235 public String getLanguage() {
236 return this.languageOrDatatype instanceof String ? (String) this.languageOrDatatype
237 : null;
238 }
239
240 @Override
241 public URI getDatatype() {
242 if (this.languageOrDatatype instanceof URI) {
243 return (URI) this.languageOrDatatype;
244 } else if (this.languageOrDatatype instanceof String) {
245 return RDF.LANGSTRING;
246 } else {
247 return XMLSchema.STRING;
248 }
249 }
250
251 @Override
252 public String stringValue() {
253 return this.label;
254 }
255
256 @Override
257 public boolean booleanValue() {
258 return XMLDatatypeUtil.parseBoolean(this.label);
259 }
260
261 @Override
262 public byte byteValue() {
263 return XMLDatatypeUtil.parseByte(this.label);
264 }
265
266 @Override
267 public short shortValue() {
268 return XMLDatatypeUtil.parseShort(this.label);
269 }
270
271 @Override
272 public int intValue() {
273 return XMLDatatypeUtil.parseInt(this.label);
274 }
275
276 @Override
277 public long longValue() {
278 return XMLDatatypeUtil.parseLong(this.label);
279 }
280
281 @Override
282 public float floatValue() {
283 return XMLDatatypeUtil.parseFloat(this.label);
284 }
285
286 @Override
287 public double doubleValue() {
288 return XMLDatatypeUtil.parseDouble(this.label);
289 }
290
291 @Override
292 public BigInteger integerValue() {
293 return XMLDatatypeUtil.parseInteger(this.label);
294 }
295
296 @Override
297 public BigDecimal decimalValue() {
298 return XMLDatatypeUtil.parseDecimal(this.label);
299 }
300
301 @Override
302 public XMLGregorianCalendar calendarValue() {
303 return XMLDatatypeUtil.parseCalendar(this.label);
304 }
305
306 @Override
307 public boolean equals(final Object object) {
308 if (object == this) {
309 return true;
310 } else if (object instanceof HashLiteral && hasHash() && ((HashURI) object).hasHash()) {
311 return sameHash((HashLiteral) object);
312 } else if (object instanceof Literal) {
313 final Literal other = (Literal) object;
314 return this.label.equals(other.getLabel())
315 && Objects.equals(getDatatype(), other.getDatatype())
316 && Objects.equals(getLanguage(), other.getLanguage());
317 } else {
318 return false;
319 }
320 }
321
322 @Override
323 public int hashCode() {
324 if (this.hashCode == 0) {
325 int hashCode = this.label.hashCode();
326 final String language = getLanguage();
327 if (language != null) {
328 hashCode = 31 * hashCode + language.hashCode();
329 }
330 hashCode = 31 * hashCode + getDatatype().hashCode();
331 this.hashCode = hashCode;
332 }
333 return this.hashCode;
334 }
335
336 @Override
337 public String toString() {
338 final StringBuilder sb = new StringBuilder(this.label.length() * 2);
339 sb.append('"');
340 sb.append(this.label);
341 sb.append('"');
342 final String language = getLanguage();
343 if (language != null) {
344 sb.append('@');
345 sb.append(language);
346 } else {
347 final URI datatype = getDatatype();
348 if (datatype != null && !datatype.equals(XMLSchema.STRING)) {
349 sb.append("^^<");
350 sb.append(datatype.stringValue());
351 sb.append(">");
352 }
353 }
354 return sb.toString();
355 }
356
357 }
358
359 private static final class HashBNode extends HashResource implements BNode {
360
361 private static final long serialVersionUID = 1L;
362
363 private final String id;
364
365 HashBNode(final String id) {
366 this.id = id;
367 }
368
369 @Override
370 public String getID() {
371 return this.id;
372 }
373
374 @Override
375 public String stringValue() {
376 return this.id;
377 }
378
379 @Override
380 public boolean equals(final Object object) {
381 if (object == this) {
382 return true;
383 } else if (object instanceof HashBNode && hasHash() && ((HashURI) object).hasHash()) {
384 return sameHash((HashBNode) object);
385 } else if (object instanceof BNode) {
386 final BNode other = (BNode) object;
387 return this.id.equals(other.getID());
388 } else {
389 return false;
390 }
391 }
392
393 @Override
394 public int hashCode() {
395 if (this.hashCode == 0) {
396 this.hashCode = this.id.hashCode();
397 }
398 return this.hashCode;
399 }
400
401 @Override
402 public String toString() {
403 return "_:" + this.id;
404 }
405
406 }
407
408 private static final class HashURI extends HashResource implements URI {
409
410 private static final long serialVersionUID = 1L;
411
412 private final String uri;
413
414 @Nullable
415 private transient String namespace;
416
417 @Nullable
418 private transient String localName;
419
420 HashURI(final String uri) {
421 this.uri = uri;
422 this.namespace = null;
423 this.localName = null;
424 }
425
426 HashURI(final String uri, final String namespace, final String localName) {
427 this.uri = uri;
428 this.namespace = namespace;
429 this.localName = localName;
430 }
431
432 private void splitURI() {
433 final int index = URIUtil.getLocalNameIndex(this.uri);
434 this.namespace = this.uri.substring(0, index);
435 this.localName = this.uri.substring(index);
436 }
437
438 @Override
439 public String getNamespace() {
440 if (this.namespace == null) {
441 splitURI();
442 }
443 return this.namespace;
444 }
445
446 @Override
447 public String getLocalName() {
448 if (this.localName == null) {
449 splitURI();
450 }
451 return this.localName;
452 }
453
454 @Override
455 public String stringValue() {
456 return this.uri;
457 }
458
459 @Override
460 public boolean equals(final Object object) {
461 if (object == this) {
462 return true;
463 } else if (object instanceof HashURI && hasHash() && ((HashURI) object).hasHash()) {
464 final HashURI other = (HashURI) object;
465 return sameHash(other);
466 } else if (object instanceof URI) {
467 return this.uri.equals(((URI) object).stringValue());
468 } else {
469 return false;
470 }
471 }
472
473 @Override
474 public int hashCode() {
475 if (this.hashCode == 0) {
476 this.hashCode = this.uri.hashCode();
477 }
478 return this.hashCode;
479 }
480
481 @Override
482 public String toString() {
483 return this.uri;
484 }
485
486 }
487
488 }