1   /*
2    * RDFpro - An extensible tool for building stream-oriented RDF processing libraries.
3    * 
4    * Written in 2015 by Francesco Corcoglioniti with support by Alessio Palmero Aprosio and Marco
5    * Rospocher. Contact info on http://rdfpro.fbk.eu/
6    * 
7    * To the extent possible under law, the authors have dedicated all copyright and related and
8    * neighboring rights to this software to the public domain worldwide. This software is
9    * distributed without any warranty.
10   * 
11   * You should have received a copy of the CC0 Public Domain Dedication along with this software.
12   * If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
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 }