1
2
3
4
5
6
7
8
9
10
11
12
13
14 package eu.fbk.rdfpro.util;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.math.BigInteger;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.Date;
25 import java.util.GregorianCalendar;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.Set;
30 import java.util.concurrent.atomic.AtomicInteger;
31 import java.util.concurrent.atomic.AtomicLong;
32 import java.util.function.Function;
33 import java.util.function.Predicate;
34
35 import javax.annotation.Nullable;
36 import javax.xml.datatype.DatatypeFactory;
37 import javax.xml.datatype.XMLGregorianCalendar;
38
39 import org.openrdf.model.BNode;
40 import org.openrdf.model.Literal;
41 import org.openrdf.model.Resource;
42 import org.openrdf.model.Statement;
43 import org.openrdf.model.URI;
44 import org.openrdf.model.Value;
45 import org.openrdf.model.ValueFactory;
46 import org.openrdf.model.impl.ContextStatementImpl;
47 import org.openrdf.model.impl.StatementImpl;
48 import org.openrdf.model.impl.ValueFactoryImpl;
49 import org.openrdf.model.vocabulary.OWL;
50 import org.openrdf.model.vocabulary.RDF;
51 import org.openrdf.model.vocabulary.RDFS;
52 import org.openrdf.model.vocabulary.XMLSchema;
53 import org.openrdf.rio.RDFFormat;
54 import org.openrdf.rio.Rio;
55
56 public final class Statements {
57
58 public static final ValueFactory VALUE_FACTORY;
59
60 static {
61 final boolean hashfactory = Boolean.parseBoolean(Environment.getProperty(
62 "rdfpro.hashfactory", "true"));
63 if (hashfactory) {
64 VALUE_FACTORY = HashValueFactory.INSTANCE;
65 } else {
66 VALUE_FACTORY = ValueFactoryImpl.getInstance();
67 }
68 }
69
70 public static final Function<Value, Value> VALUE_NORMALIZER = new Function<Value, Value>() {
71
72 @Override
73 public Value apply(final Value value) {
74 return normalize(value);
75 }
76
77 };
78
79 public static final DatatypeFactory DATATYPE_FACTORY;
80
81 public static final Set<URI> TBOX_CLASSES = Collections.unmodifiableSet(new HashSet<URI>(
82 Arrays.asList(RDFS.CLASS, RDFS.DATATYPE, RDF.PROPERTY,
83 VALUE_FACTORY.createURI(OWL.NAMESPACE, "AllDisjointClasses"),
84 VALUE_FACTORY.createURI(OWL.NAMESPACE, "AllDisjointProperties"),
85 OWL.ANNOTATIONPROPERTY,
86 VALUE_FACTORY.createURI(OWL.NAMESPACE, "AsymmetricProperty"), OWL.CLASS,
87 OWL.DATATYPEPROPERTY, OWL.FUNCTIONALPROPERTY, OWL.INVERSEFUNCTIONALPROPERTY,
88 VALUE_FACTORY.createURI(OWL.NAMESPACE, "IrreflexiveProperty"),
89 OWL.OBJECTPROPERTY, OWL.ONTOLOGY,
90 VALUE_FACTORY.createURI(OWL.NAMESPACE, "ReflexiveProperty"), OWL.RESTRICTION,
91 OWL.SYMMETRICPROPERTY, OWL.TRANSITIVEPROPERTY)));
92
93
94
95
96 public static final Set<URI> TBOX_PROPERTIES = Collections.unmodifiableSet(new HashSet<URI>(
97 Arrays.asList(RDF.FIRST, RDF.REST, RDFS.DOMAIN, RDFS.RANGE, RDFS.SUBCLASSOF,
98 RDFS.SUBPROPERTYOF, OWL.ALLVALUESFROM, OWL.CARDINALITY, OWL.COMPLEMENTOF,
99 VALUE_FACTORY.createURI(OWL.NAMESPACE, "datatypeComplementOf"),
100 VALUE_FACTORY.createURI(OWL.NAMESPACE, "disjointUnionOf"), OWL.DISJOINTWITH,
101 OWL.EQUIVALENTCLASS, OWL.EQUIVALENTPROPERTY,
102 VALUE_FACTORY.createURI(OWL.NAMESPACE, "hasKey"),
103 VALUE_FACTORY.createURI(OWL.NAMESPACE, "hasSelf"), OWL.HASVALUE, OWL.IMPORTS,
104 OWL.INTERSECTIONOF, OWL.INVERSEOF, OWL.MAXCARDINALITY,
105 VALUE_FACTORY.createURI(OWL.NAMESPACE, "maxQualifiedCardinality"),
106 VALUE_FACTORY.createURI(OWL.NAMESPACE, "members"), OWL.MINCARDINALITY,
107 VALUE_FACTORY.createURI(OWL.NAMESPACE, "minQualifiedCardinality"),
108 VALUE_FACTORY.createURI(OWL.NAMESPACE, "onClass"),
109 VALUE_FACTORY.createURI(OWL.NAMESPACE, "onDataRange"),
110 VALUE_FACTORY.createURI(OWL.NAMESPACE, "onDataType"),
111 VALUE_FACTORY.createURI(OWL.NAMESPACE, "onProperties"), OWL.ONPROPERTY,
112 OWL.ONEOF, VALUE_FACTORY.createURI(OWL.NAMESPACE, "propertyChainAxiom"),
113 VALUE_FACTORY.createURI(OWL.NAMESPACE, "propertyDisjointWith"),
114 VALUE_FACTORY.createURI(OWL.NAMESPACE, "qualifiedCardinality"),
115 OWL.SOMEVALUESFROM, OWL.UNIONOF, OWL.VERSIONIRI,
116 VALUE_FACTORY.createURI(OWL.NAMESPACE, "withRestrictions"))));
117
118 private static final Comparator<Value> DEFAULT_VALUE_ORDERING = new ValueComparator();
119
120 private static final Comparator<Statement> DEFAULT_STATEMENT_ORDERING = new StatementComparator(
121 "spoc", new ValueComparator(RDF.NAMESPACE));
122 static {
123 try {
124 DATATYPE_FACTORY = DatatypeFactory.newInstance();
125 } catch (final Throwable ex) {
126 throw new Error("Unexpected exception (!): " + ex.getMessage(), ex);
127 }
128 }
129
130 private static final Hash NIL_HASH = Hash.murmur3("\u0001");
131
132 public static Comparator<Value> valueComparator(final String... rankedNamespaces) {
133 return rankedNamespaces == null || rankedNamespaces.length == 0 ? DEFAULT_VALUE_ORDERING
134 : new ValueComparator(rankedNamespaces);
135 }
136
137 public static Comparator<Statement> statementComparator(@Nullable final String components,
138 @Nullable final Comparator<? super Value> valueComparator) {
139 if (components == null) {
140 return valueComparator == null ? DEFAULT_STATEMENT_ORDERING
141 : new StatementComparator("spoc", valueComparator);
142 } else {
143 return new StatementComparator(components,
144 valueComparator == null ? DEFAULT_VALUE_ORDERING : valueComparator);
145 }
146 }
147
148 @SuppressWarnings("unchecked")
149 @Nullable
150 public static Predicate<Statement> statementMatcher(@Nullable final String spec) {
151 if (spec == null) {
152 return null;
153 } else if (Scripting.isScript(spec)) {
154 return Scripting.compile(Predicate.class, spec, "q");
155 } else {
156 return new StatementMatcher(spec);
157 }
158 }
159
160 public static Statement normalize(final Statement statement) {
161 final Resource subj = normalize(statement.getSubject());
162 final URI pred = normalize(statement.getPredicate());
163 final Value obj = normalize(statement.getObject());
164 final Resource ctx = normalize(statement.getContext());
165 if (subj == statement.getSubject() && pred == statement.getPredicate()
166 && obj == statement.getObject() && ctx == statement.getContext()) {
167 return statement;
168 } else if (ctx != null) {
169 return new ContextStatementImpl(subj, pred, obj, ctx);
170 } else {
171 return new StatementImpl(subj, pred, obj);
172 }
173 }
174
175 @Nullable
176 public static <T extends Value> T normalize(@Nullable final T value) {
177 if (VALUE_FACTORY instanceof HashValueFactory) {
178 return HashValueFactory.normalize(value);
179 }
180 return value;
181 }
182
183 public static Hash getHash(final Statement statement) {
184 return statement instanceof Hashable ? ((Hashable) statement).getHash()
185 : computeHash(statement);
186 }
187
188 public static Hash getHash(@Nullable final Value value) {
189 return value instanceof Hashable ? ((Hashable) value).getHash() : computeHash(value);
190 }
191
192 public static Hash computeHash(final Statement statement) {
193 final Hash subjHash = getHash(statement.getSubject());
194 final Hash predHash = getHash(statement.getPredicate());
195 final Hash objHash = getHash(statement.getObject());
196 final Hash ctxHash = getHash(statement.getContext());
197 return Hash.combine(subjHash, predHash, objHash, ctxHash);
198 }
199
200 public static Hash computeHash(@Nullable final Value value) {
201 if (value == null) {
202 return NIL_HASH;
203 }
204 Hash hash;
205 if (value instanceof URI) {
206 hash = Hash.murmur3("\u0001", value.stringValue());
207 } else if (value instanceof BNode) {
208 hash = Hash.murmur3("\u0002", ((BNode) value).getID());
209 } else {
210 final Literal l = (Literal) value;
211 if (l.getLanguage() != null) {
212 hash = Hash.murmur3("\u0003", l.getLanguage(), l.getLabel());
213 } else if (l.getDatatype() != null) {
214 hash = Hash.murmur3("\u0004", l.getDatatype().stringValue(), l.getLabel());
215 } else {
216 hash = Hash.murmur3("\u0005", l.getLabel());
217 }
218 }
219 if (hash.getLow() == 0) {
220 hash = Hash.fromLongs(hash.getHigh(), 1L);
221 }
222 return hash;
223 }
224
225 @Nullable
226 public static File toRDFFile(final String fileSpec) {
227 final int index = fileSpec.indexOf(':');
228 if (index > 0) {
229 final String name = "test." + fileSpec.substring(0, index);
230 if (Rio.getParserFormatForFileName(name) != null
231 || Rio.getWriterFormatForFileName(name) != null) {
232 return new File(fileSpec.substring(index + 1));
233 }
234 }
235 return new File(fileSpec);
236 }
237
238 public static RDFFormat toRDFFormat(final String fileSpec) {
239 final int index = fileSpec.indexOf(':');
240 RDFFormat format = null;
241 if (index > 0) {
242 final String name = "test." + fileSpec.substring(0, index);
243 format = Rio.getParserFormatForFileName(name);
244 if (format == null) {
245 format = Rio.getWriterFormatForFileName(name);
246 }
247 }
248 if (format == null) {
249 format = Rio.getParserFormatForFileName(fileSpec);
250 if (format == null) {
251 format = Rio.getWriterFormatForFileName(fileSpec);
252 }
253 }
254 if (format == null) {
255 throw new IllegalArgumentException("Unknown RDF format for " + fileSpec);
256 }
257 return format;
258 }
259
260 public static boolean isRDFFormatTextBased(final RDFFormat format) {
261 for (final String ext : format.getFileExtensions()) {
262 if (ext.equalsIgnoreCase("rdf") || ext.equalsIgnoreCase("rj")
263 || ext.equalsIgnoreCase("jsonld") || ext.equalsIgnoreCase("nt")
264 || ext.equalsIgnoreCase("nq") || ext.equalsIgnoreCase("trix")
265 || ext.equalsIgnoreCase("trig") || ext.equalsIgnoreCase("tql")
266 || ext.equalsIgnoreCase("ttl") || ext.equalsIgnoreCase("n3")) {
267 return true;
268 }
269 }
270 return false;
271 }
272
273 public static boolean isRDFFormatLineBased(final RDFFormat format) {
274 for (final String ext : format.getFileExtensions()) {
275 if (ext.equalsIgnoreCase("nt") || ext.equalsIgnoreCase("nq")
276 || ext.equalsIgnoreCase("tql")) {
277 return true;
278 }
279 }
280 return false;
281 }
282
283 public static Value shortenValue(final Value value, final int threshold) {
284 if (value instanceof Literal) {
285 final Literal literal = (Literal) value;
286 final URI datatype = literal.getDatatype();
287 final String language = literal.getLanguage();
288 final String label = ((Literal) value).getLabel();
289 if (label.length() > threshold
290 && (datatype == null || datatype.equals(XMLSchema.STRING))) {
291 int offset = threshold;
292 for (int i = threshold; i >= 0; --i) {
293 if (Character.isWhitespace(label.charAt(i))) {
294 offset = i;
295 break;
296 }
297 }
298 final String newLabel = label.substring(0, offset) + "...";
299 if (language != null) {
300 return VALUE_FACTORY.createLiteral(newLabel, language);
301 } else if (datatype != null) {
302 return VALUE_FACTORY.createLiteral(newLabel, datatype);
303 } else {
304 return VALUE_FACTORY.createLiteral(newLabel);
305 }
306 }
307 }
308 return value;
309 }
310
311 @Nullable
312 public static String formatValue(@Nullable final Value value) {
313 return formatValue(value, null);
314 }
315
316 public static String formatValue(@Nullable final Value value,
317 @Nullable final Namespaces namespaces) {
318 if (value == null) {
319 return null;
320 }
321 try {
322 final StringBuilder builder = new StringBuilder(value.stringValue().length() * 2);
323 formatValue(value, namespaces, builder);
324 return builder.toString();
325 } catch (final Throwable ex) {
326 throw new Error("Unexpected exception (!)", ex);
327 }
328 }
329
330 public static void formatValue(final Value value, @Nullable final Namespaces namespaces,
331 final Appendable out) throws IOException {
332 if (value instanceof URI) {
333 formatURI((URI) value, out, namespaces);
334 } else if (value instanceof BNode) {
335 formatBNode((BNode) value, out);
336 } else if (value instanceof Literal) {
337 formatLiteral((Literal) value, out, namespaces);
338 } else {
339 throw new Error("Unexpected value class (!): " + value.getClass().getName());
340 }
341 }
342
343 private static boolean isGoodQName(final String prefix, final String name) {
344
345 final int prefixLen = prefix.length();
346 if (prefixLen > 0) {
347 if (!isPN_CHARS_BASE(prefix.charAt(0)) || !isPN_CHARS(prefix.charAt(prefixLen - 1))) {
348 return false;
349 }
350 for (int i = 1; i < prefixLen - 1; ++i) {
351 final char c = prefix.charAt(i);
352 if (!isPN_CHARS(c) && c != '.') {
353 return false;
354 }
355 }
356 }
357
358 final int nameLen = name.length();
359 if (nameLen > 0) {
360 int i = 0;
361 while (i < nameLen) {
362 final char c = name.charAt(i++);
363 if (!isPN_CHARS_BASE(c) && c != ':' && (i != 1 || c != '_' && !isNumber(c))
364 && (i == 1 || !isPN_CHARS(c) && (i == nameLen || c != '.'))) {
365 return false;
366 }
367 }
368 }
369
370 return true;
371 }
372
373 private static void formatURI(final URI uri, final Appendable out,
374 @Nullable final Namespaces namespaces) throws IOException {
375
376 if (namespaces != null) {
377 final String prefix = namespaces.prefixFor(uri.getNamespace());
378 if (prefix != null) {
379 final String name = uri.getLocalName();
380 if (isGoodQName(prefix, name)) {
381 out.append(prefix);
382 out.append(":");
383 out.append(name);
384 return;
385 }
386 }
387 }
388
389 final String string = uri.stringValue();
390 final int len = string.length();
391 out.append('<');
392 for (int i = 0; i < len; ++i) {
393 final char ch = string.charAt(i);
394 switch (ch) {
395 case 0x22:
396 out.append("\\u0022");
397 break;
398 case 0x3C:
399 out.append("\\u003C");
400 break;
401 case 0x3E:
402 out.append("\\u003E");
403 break;
404 case 0x5C:
405 out.append("\\u005C");
406 break;
407 case 0x5E:
408 out.append("\\u005E");
409 break;
410 case 0x60:
411 out.append("\\u0060");
412 break;
413 case 0x7B:
414 out.append("\\u007B");
415 break;
416 case 0x7C:
417 out.append("\\u007C");
418 break;
419 case 0x7D:
420 out.append("\\u007D");
421 break;
422 case 0x7F:
423 out.append("\\u007F");
424 break;
425 default:
426 if (ch <= 32) {
427 out.append("\\u00").append(Character.forDigit(ch / 16, 16))
428 .append(Character.forDigit(ch % 16, 16));
429 } else {
430 out.append(ch);
431 }
432 }
433 }
434 out.append('>');
435 }
436
437 private static void formatBNode(final BNode bnode, final Appendable out) throws IOException {
438 final String id = bnode.getID();
439 final int last = id.length() - 1;
440 out.append('_').append(':');
441 if (last < 0) {
442 out.append("genid-hash-").append(Integer.toHexString(System.identityHashCode(bnode)));
443 } else {
444 char ch = id.charAt(0);
445 if (isPN_CHARS_U(ch) || isNumber(ch)) {
446 out.append(ch);
447 } else {
448 out.append("genid-start-").append(ch);
449 }
450 if (last > 0) {
451 for (int i = 1; i < last; ++i) {
452 ch = id.charAt(i);
453 if (isPN_CHARS(ch) || ch == '.') {
454 out.append(ch);
455 } else {
456 out.append(Integer.toHexString(ch));
457 }
458 }
459 ch = id.charAt(last);
460 if (isPN_CHARS(ch)) {
461 out.append(ch);
462 } else {
463 out.append(Integer.toHexString(ch));
464 }
465 }
466 }
467 }
468
469 private static void formatLiteral(final Literal literal, final Appendable out,
470 @Nullable final Namespaces namespaces) throws IOException {
471 final String label = literal.getLabel();
472 final int length = label.length();
473 out.append('"');
474 for (int i = 0; i < length; ++i) {
475 final char ch = label.charAt(i);
476 switch (ch) {
477 case 0x08:
478 out.append('\\');
479 out.append('b');
480 break;
481 case 0x09:
482 out.append('\\');
483 out.append('t');
484 break;
485 case 0x0A:
486 out.append('\\');
487 out.append('n');
488 break;
489 case 0x0C:
490 out.append('\\');
491 out.append('f');
492 break;
493 case 0x0D:
494 out.append('\\');
495 out.append('r');
496 break;
497 case 0x22:
498 out.append('\\');
499 out.append('"');
500 break;
501 case 0x5C:
502 out.append('\\');
503 out.append('\\');
504 break;
505 case 0x7F:
506 out.append("\\u007F");
507 break;
508 default:
509 if (ch < 32) {
510 out.append("\\u00");
511 out.append(Character.forDigit(ch / 16, 16));
512 out.append(Character.forDigit(ch % 16, 16));
513 } else {
514 out.append(ch);
515 }
516 }
517 }
518 out.append('"');
519 final String language = literal.getLanguage();
520 if (language != null) {
521 out.append('@');
522 final int len = language.length();
523 boolean minusFound = false;
524 boolean valid = true;
525 for (int i = 0; i < len; ++i) {
526 final char ch = language.charAt(i);
527 if (ch == '-') {
528 minusFound = true;
529 if (i == 0) {
530 valid = false;
531 } else {
532 final char prev = language.charAt(i - 1);
533 valid &= isLetter(prev) || isNumber(prev);
534 }
535 } else if (isNumber(ch)) {
536 valid &= minusFound;
537 } else {
538 valid &= isLetter(ch);
539 }
540 out.append(ch);
541 }
542 if (!valid || language.charAt(len - 1) == '-') {
543 throw new IllegalArgumentException("Invalid language tag '" + language + "' in '"
544 + literal + "'");
545 }
546 } else {
547 final URI datatype = literal.getDatatype();
548 if (datatype != null && !XMLSchema.STRING.equals(datatype)) {
549 out.append('^');
550 out.append('^');
551 formatURI(datatype, out, namespaces);
552 }
553 }
554 }
555
556 @Nullable
557 public static Value parseValue(@Nullable final CharSequence sequence) {
558 return parseValue(sequence, null);
559 }
560
561 public static Value parseValue(@Nullable final CharSequence sequence,
562 @Nullable final Namespaces namespaces) {
563 if (sequence == null) {
564 return null;
565 }
566 final int c = sequence.charAt(0);
567 if (c == '_') {
568 return parseBNode(sequence);
569 } else if (c == '"' || c == '\'') {
570 return parseLiteral(sequence, namespaces);
571 } else {
572 return parseURI(sequence, namespaces);
573 }
574 }
575
576 private static URI parseURI(final CharSequence sequence, @Nullable final Namespaces namespaces) {
577
578 if (sequence.charAt(0) == '<') {
579 final int last = sequence.length() - 1;
580 final StringBuilder builder = new StringBuilder(last - 1);
581 if (sequence.charAt(last) != '>') {
582 throw new IllegalArgumentException("Invalid URI: " + sequence);
583 }
584 int i = 1;
585 while (i < last) {
586 char c = sequence.charAt(i++);
587 if (c < 32) {
588 throw new IllegalArgumentException("Invalid char '" + c + "' in URI: "
589 + sequence);
590 } else if (c != '\\') {
591 builder.append(c);
592 } else {
593 if (i == last) {
594 throw new IllegalArgumentException("Invalid URI: " + sequence);
595 }
596 c = sequence.charAt(i++);
597 if (c == 'u') {
598 builder.append(parseHex(sequence, i, 4));
599 i += 4;
600 } else if (c == 'U') {
601 builder.append(parseHex(sequence, i, 8));
602 i += 8;
603 } else {
604 builder.append(c);
605 }
606 }
607 }
608 return VALUE_FACTORY.createURI(builder.toString());
609
610 } else if (namespaces != null) {
611 final int len = sequence.length();
612 final StringBuilder builder = new StringBuilder(len);
613
614 int i = 0;
615 while (i < len) {
616 final char c = sequence.charAt(i++);
617 if (c == ':') {
618 if (i > 2 && !isPN_CHARS(sequence.charAt(i - 2))) {
619 throw new IllegalArgumentException("Invalid qname " + sequence);
620 }
621 break;
622 } else if (i == 1 && !isPN_CHARS_BASE(c) || i > 1 && !isPN_CHARS(c) && c != '.') {
623 throw new IllegalArgumentException("Invalid qname " + sequence);
624 } else {
625 builder.append(c);
626 }
627 }
628 final String prefix = builder.toString();
629 final String namespace = namespaces.uriFor(prefix);
630 if (namespace == null) {
631 throw new IllegalArgumentException("Unknown prefix " + prefix);
632 }
633 builder.setLength(0);
634
635 while (i < len) {
636 final char c = sequence.charAt(i++);
637 if (c == '%') {
638 builder.append(parseHex(sequence, i, 2));
639 i += 2;
640 } else if (c == '\\') {
641 final char d = sequence.charAt(i++);
642 if (d == '_' || d == '~' || d == '.' || d == '-' || d == '!' || d == '$'
643 || d == '&' || d == '\'' || d == '(' || d == ')' || d == '*'
644 || d == '+' || d == ',' || d == ';' || d == '=' || d == '/'
645 || d == '?' || d == '#' || d == '@' || d == '%') {
646 builder.append(d);
647 } else {
648 throw new IllegalArgumentException("Invalid qname " + sequence);
649 }
650 } else if (isPN_CHARS_BASE(c) || c == ':' || i == 1 && (c == '_' || isNumber(c))
651 || i > 1 && (isPN_CHARS(c) || i < len && c == '.')) {
652 builder.append(c);
653 } else {
654 throw new IllegalArgumentException("Invalid qname " + sequence);
655 }
656 }
657 final String name = builder.toString();
658
659 return VALUE_FACTORY.createURI(namespace, name);
660 }
661
662 throw new IllegalArgumentException("Unsupported qname " + sequence);
663 }
664
665 private static BNode parseBNode(final CharSequence sequence) {
666 final int len = sequence.length();
667 if (len > 2 && sequence.charAt(1) == ':') {
668 final StringBuilder builder = new StringBuilder(len - 2);
669 boolean ok = true;
670 char c = sequence.charAt(2);
671 builder.append(c);
672 ok &= isPN_CHARS_U(c) || isNumber(c);
673 for (int i = 2; i < len - 1; ++i) {
674 c = sequence.charAt(i);
675 builder.append(c);
676 ok &= isPN_CHARS(c) || c == '.';
677 }
678 c = sequence.charAt(len - 1);
679 builder.append(c);
680 ok &= isPN_CHARS(c);
681 if (ok) {
682 return VALUE_FACTORY.createBNode(builder.toString());
683 }
684 }
685 throw new IllegalArgumentException("Invalid BNode '" + sequence + "'");
686 }
687
688 private static Literal parseLiteral(final CharSequence sequence,
689 @Nullable final Namespaces namespaces) {
690
691 final StringBuilder builder = new StringBuilder(sequence.length());
692 final int len = sequence.length();
693
694 final char delim = sequence.charAt(0);
695 char c = 0;
696 int i = 1;
697 while (i < len && (c = sequence.charAt(i++)) != delim) {
698 if (c == '\\' && i < len) {
699 c = sequence.charAt(i++);
700 switch (c) {
701 case 'b':
702 builder.append('\b');
703 break;
704 case 'f':
705 builder.append('\f');
706 break;
707 case 'n':
708 builder.append('\n');
709 break;
710 case 'r':
711 builder.append('\r');
712 break;
713 case 't':
714 builder.append('\t');
715 break;
716 case 'u':
717 builder.append(parseHex(sequence, i, 4));
718 i += 4;
719 break;
720 case 'U':
721 builder.append(parseHex(sequence, i, 8));
722 i += 8;
723 break;
724 default:
725 builder.append(c);
726 break;
727 }
728 } else {
729 builder.append(c);
730 }
731 }
732 final String label = builder.toString();
733
734 if (i == len && c == delim) {
735 return VALUE_FACTORY.createLiteral(label);
736
737 } else if (i < len - 2 && sequence.charAt(i) == '^' && sequence.charAt(i + 1) == '^') {
738 final URI datatype = parseURI(sequence.subSequence(i + 2, len), namespaces);
739 return VALUE_FACTORY.createLiteral(label, datatype);
740
741 } else if (i < len - 1 && sequence.charAt(i) == '@') {
742 builder.setLength(0);
743 boolean minusFound = false;
744 for (int j = i + 1; j < len; ++j) {
745 c = sequence.charAt(j);
746 if (!isLetter(c) && (c != '-' || j == i + 1 || j == len - 1)
747 && (!isNumber(c) || !minusFound)) {
748 throw new IllegalArgumentException("Invalid lang in '" + sequence + "'");
749 }
750 minusFound |= c == '-';
751 builder.append(c);
752 }
753 return VALUE_FACTORY.createLiteral(label, builder.toString());
754 }
755
756 throw new IllegalArgumentException("Invalid literal '" + sequence + "'");
757 }
758
759 private static char parseHex(final CharSequence sequence, final int index, final int count) {
760 int code = 0;
761 final int len = sequence.length();
762 if (index + count >= len) {
763 throw new IllegalArgumentException("Incomplete hex code '"
764 + sequence.subSequence(index, len) + "' in RDF value '" + sequence + "'");
765 }
766 for (int i = 0; i < count; ++i) {
767 final char c = sequence.charAt(index + i);
768 final int digit = Character.digit(c, 16);
769 if (digit < 0) {
770 throw new IllegalArgumentException("Invalid hex digit '" + c + "' in RDF value '"
771 + sequence + "'");
772 }
773 code = code * 16 + digit;
774 }
775 return (char) code;
776 }
777
778 private static boolean isPN_CHARS(final int c) {
779 return isPN_CHARS_U(c) || isNumber(c) || c == '-' || c == 0x00B7 || c >= 0x0300
780 && c <= 0x036F || c >= 0x203F && c <= 0x2040;
781 }
782
783 private static boolean isPN_CHARS_U(final int c) {
784 return isPN_CHARS_BASE(c) || c == '_';
785 }
786
787 private static boolean isPN_CHARS_BASE(final int c) {
788 return isLetter(c) || c >= 0x00C0 && c <= 0x00D6 || c >= 0x00D8 && c <= 0x00F6
789 || c >= 0x00F8 && c <= 0x02FF || c >= 0x0370 && c <= 0x037D || c >= 0x037F
790 && c <= 0x1FFF || c >= 0x200C && c <= 0x200D || c >= 0x2070 && c <= 0x218F
791 || c >= 0x2C00 && c <= 0x2FEF || c >= 0x3001 && c <= 0xD7FF || c >= 0xF900
792 && c <= 0xFDCF || c >= 0xFDF0 && c <= 0xFFFD || c >= 0x10000 && c <= 0xEFFFF;
793 }
794
795 private static boolean isLetter(final int c) {
796 return c >= 65 && c <= 90 || c >= 97 && c <= 122;
797 }
798
799 private static boolean isNumber(final int c) {
800 return c >= 48 && c <= 57;
801 }
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866 @SuppressWarnings("unchecked")
867 @Nullable
868 public static <T> T convert(@Nullable final Object object, final Class<T> clazz)
869 throws IllegalArgumentException {
870 if (object == null) {
871 Objects.requireNonNull(clazz);
872 return null;
873 }
874 if (clazz.isInstance(object)) {
875 return (T) object;
876 }
877 final T result = (T) convertObject(object, clazz);
878 if (result != null) {
879 return result;
880 }
881 throw new IllegalArgumentException("Unsupported conversion of " + object + " to " + clazz);
882 }
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900 @SuppressWarnings("unchecked")
901 @Nullable
902 public static <T> T convert(@Nullable final Object object, final Class<T> clazz,
903 @Nullable final T defaultValue) {
904 if (object == null) {
905 Objects.requireNonNull(clazz);
906 return defaultValue;
907 }
908 if (clazz.isInstance(object)) {
909 return (T) object;
910 }
911 try {
912 final T result = (T) convertObject(object, clazz);
913 return result != null ? result : defaultValue;
914 } catch (final RuntimeException ex) {
915 return defaultValue;
916 }
917 }
918
919 @Nullable
920 private static Object convertObject(final Object object, final Class<?> clazz) {
921 if (object instanceof Literal) {
922 return convertLiteral((Literal) object, clazz);
923 } else if (object instanceof URI) {
924 return convertURI((URI) object, clazz);
925 } else if (object instanceof String) {
926 return convertString((String) object, clazz);
927 } else if (object instanceof Number) {
928 return convertNumber((Number) object, clazz);
929 } else if (object instanceof Boolean) {
930 return convertBoolean((Boolean) object, clazz);
931 } else if (object instanceof XMLGregorianCalendar) {
932 return convertCalendar((XMLGregorianCalendar) object, clazz);
933 } else if (object instanceof BNode) {
934 return convertBNode((BNode) object, clazz);
935 } else if (object instanceof Statement) {
936 return convertStatement((Statement) object, clazz);
937 } else if (object instanceof GregorianCalendar) {
938 final XMLGregorianCalendar calendar = DATATYPE_FACTORY
939 .newXMLGregorianCalendar((GregorianCalendar) object);
940 return clazz == XMLGregorianCalendar.class ? calendar : convertCalendar(calendar,
941 clazz);
942 } else if (object instanceof Date) {
943 final GregorianCalendar calendar = new GregorianCalendar();
944 calendar.setTime((Date) object);
945 final XMLGregorianCalendar xmlCalendar = DATATYPE_FACTORY
946 .newXMLGregorianCalendar(calendar);
947 return clazz == XMLGregorianCalendar.class ? xmlCalendar : convertCalendar(
948 xmlCalendar, clazz);
949 } else if (object instanceof Enum<?>) {
950 return convertEnum((Enum<?>) object, clazz);
951 } else if (object instanceof File) {
952 return convertFile((File) object, clazz);
953 }
954 return null;
955 }
956
957 @Nullable
958 private static Object convertStatement(final Statement statement, final Class<?> clazz) {
959 if (clazz.isAssignableFrom(String.class)) {
960 return statement.toString();
961 }
962 return null;
963 }
964
965 @Nullable
966 private static Object convertLiteral(final Literal literal, final Class<?> clazz) {
967 final URI datatype = literal.getDatatype();
968 if (datatype == null || datatype.equals(XMLSchema.STRING)) {
969 return convertString(literal.getLabel(), clazz);
970 } else if (datatype.equals(XMLSchema.BOOLEAN)) {
971 return convertBoolean(literal.booleanValue(), clazz);
972 } else if (datatype.equals(XMLSchema.DATE) || datatype.equals(XMLSchema.DATETIME)) {
973 return convertCalendar(literal.calendarValue(), clazz);
974 } else if (datatype.equals(XMLSchema.INT)) {
975 return convertNumber(literal.intValue(), clazz);
976 } else if (datatype.equals(XMLSchema.LONG)) {
977 return convertNumber(literal.longValue(), clazz);
978 } else if (datatype.equals(XMLSchema.DOUBLE)) {
979 return convertNumber(literal.doubleValue(), clazz);
980 } else if (datatype.equals(XMLSchema.FLOAT)) {
981 return convertNumber(literal.floatValue(), clazz);
982 } else if (datatype.equals(XMLSchema.SHORT)) {
983 return convertNumber(literal.shortValue(), clazz);
984 } else if (datatype.equals(XMLSchema.BYTE)) {
985 return convertNumber(literal.byteValue(), clazz);
986 } else if (datatype.equals(XMLSchema.DECIMAL)) {
987 return convertNumber(literal.decimalValue(), clazz);
988 } else if (datatype.equals(XMLSchema.INTEGER)) {
989 return convertNumber(literal.integerValue(), clazz);
990 } else if (datatype.equals(XMLSchema.NON_NEGATIVE_INTEGER)
991 || datatype.equals(XMLSchema.NON_POSITIVE_INTEGER)
992 || datatype.equals(XMLSchema.NEGATIVE_INTEGER)
993 || datatype.equals(XMLSchema.POSITIVE_INTEGER)) {
994 return convertNumber(literal.integerValue(), clazz);
995 } else if (datatype.equals(XMLSchema.NORMALIZEDSTRING) || datatype.equals(XMLSchema.TOKEN)
996 || datatype.equals(XMLSchema.NMTOKEN) || datatype.equals(XMLSchema.LANGUAGE)
997 || datatype.equals(XMLSchema.NAME) || datatype.equals(XMLSchema.NCNAME)) {
998 return convertString(literal.getLabel(), clazz);
999 }
1000 return null;
1001 }
1002
1003 @Nullable
1004 private static Object convertBoolean(final Boolean bool, final Class<?> clazz) {
1005 if (clazz == Boolean.class || clazz == boolean.class) {
1006 return bool;
1007 } else if (clazz.isAssignableFrom(Literal.class)) {
1008 return VALUE_FACTORY.createLiteral(bool);
1009 } else if (clazz.isAssignableFrom(String.class)) {
1010 return bool.toString();
1011 }
1012 return null;
1013 }
1014
1015 @Nullable
1016 private static Object convertString(final String string, final Class<?> clazz) {
1017 if (clazz.isInstance(string)) {
1018 return string;
1019 } else if (clazz.isAssignableFrom(Literal.class)) {
1020 return VALUE_FACTORY.createLiteral(string, XMLSchema.STRING);
1021 } else if (clazz.isAssignableFrom(URI.class)) {
1022 return VALUE_FACTORY.createURI(string);
1023 } else if (clazz.isAssignableFrom(BNode.class)) {
1024 return VALUE_FACTORY.createBNode(string.startsWith("_:") ? string.substring(2)
1025 : string);
1026 } else if (clazz == Boolean.class || clazz == boolean.class) {
1027 return Boolean.valueOf(string);
1028 } else if (clazz == Integer.class || clazz == int.class) {
1029 return Integer.valueOf((int) toLong(string));
1030 } else if (clazz == Long.class || clazz == long.class) {
1031 return Long.valueOf(toLong(string));
1032 } else if (clazz == Double.class || clazz == double.class) {
1033 return Double.valueOf(string);
1034 } else if (clazz == Float.class || clazz == float.class) {
1035 return Float.valueOf(string);
1036 } else if (clazz == Short.class || clazz == short.class) {
1037 return Short.valueOf((short) toLong(string));
1038 } else if (clazz == Byte.class || clazz == byte.class) {
1039 return Byte.valueOf((byte) toLong(string));
1040 } else if (clazz == BigDecimal.class) {
1041 return new BigDecimal(string);
1042 } else if (clazz == BigInteger.class) {
1043 return new BigInteger(string);
1044 } else if (clazz == AtomicInteger.class) {
1045 return new AtomicInteger(Integer.parseInt(string));
1046 } else if (clazz == AtomicLong.class) {
1047 return new AtomicLong(Long.parseLong(string));
1048 } else if (clazz == Date.class) {
1049 final String fixed = string.contains("T") ? string : string + "T00:00:00";
1050 return DATATYPE_FACTORY.newXMLGregorianCalendar(fixed).toGregorianCalendar().getTime();
1051 } else if (clazz.isAssignableFrom(GregorianCalendar.class)) {
1052 final String fixed = string.contains("T") ? string : string + "T00:00:00";
1053 return DATATYPE_FACTORY.newXMLGregorianCalendar(fixed).toGregorianCalendar();
1054 } else if (clazz.isAssignableFrom(XMLGregorianCalendar.class)) {
1055 final String fixed = string.contains("T") ? string : string + "T00:00:00";
1056 return DATATYPE_FACTORY.newXMLGregorianCalendar(fixed);
1057 } else if (clazz == Character.class || clazz == char.class) {
1058 return string.isEmpty() ? null : string.charAt(0);
1059 } else if (clazz.isEnum()) {
1060 for (final Object constant : clazz.getEnumConstants()) {
1061 if (string.equalsIgnoreCase(((Enum<?>) constant).name())) {
1062 return constant;
1063 }
1064 }
1065 throw new IllegalArgumentException("Illegal " + clazz.getSimpleName() + " constant: "
1066 + string);
1067 } else if (clazz == File.class) {
1068 return new File(string);
1069 }
1070 return null;
1071 }
1072
1073 @Nullable
1074 private static Object convertNumber(final Number number, final Class<?> clazz) {
1075 if (clazz.isAssignableFrom(Literal.class)) {
1076 if (number instanceof Integer || number instanceof AtomicInteger) {
1077 return VALUE_FACTORY.createLiteral(number.intValue());
1078 } else if (number instanceof Long || number instanceof AtomicLong) {
1079 return VALUE_FACTORY.createLiteral(number.longValue());
1080 } else if (number instanceof Double) {
1081 return VALUE_FACTORY.createLiteral(number.doubleValue());
1082 } else if (number instanceof Float) {
1083 return VALUE_FACTORY.createLiteral(number.floatValue());
1084 } else if (number instanceof Short) {
1085 return VALUE_FACTORY.createLiteral(number.shortValue());
1086 } else if (number instanceof Byte) {
1087 return VALUE_FACTORY.createLiteral(number.byteValue());
1088 } else if (number instanceof BigDecimal) {
1089 return VALUE_FACTORY.createLiteral(number.toString(), XMLSchema.DECIMAL);
1090 } else if (number instanceof BigInteger) {
1091 return VALUE_FACTORY.createLiteral(number.toString(), XMLSchema.INTEGER);
1092 }
1093 } else if (clazz.isAssignableFrom(String.class)) {
1094 return number.toString();
1095 } else if (clazz == Integer.class || clazz == int.class) {
1096 return Integer.valueOf(number.intValue());
1097 } else if (clazz == Long.class || clazz == long.class) {
1098 return Long.valueOf(number.longValue());
1099 } else if (clazz == Double.class || clazz == double.class) {
1100 return Double.valueOf(number.doubleValue());
1101 } else if (clazz == Float.class || clazz == float.class) {
1102 return Float.valueOf(number.floatValue());
1103 } else if (clazz == Short.class || clazz == short.class) {
1104 return Short.valueOf(number.shortValue());
1105 } else if (clazz == Byte.class || clazz == byte.class) {
1106 return Byte.valueOf(number.byteValue());
1107 } else if (clazz == BigDecimal.class) {
1108 return toBigDecimal(number);
1109 } else if (clazz == BigInteger.class) {
1110 return toBigInteger(number);
1111 } else if (clazz == AtomicInteger.class) {
1112 return new AtomicInteger(number.intValue());
1113 } else if (clazz == AtomicLong.class) {
1114 return new AtomicLong(number.longValue());
1115 }
1116 return null;
1117 }
1118
1119 @Nullable
1120 private static Object convertCalendar(final XMLGregorianCalendar calendar,
1121 final Class<?> clazz) {
1122 if (clazz.isInstance(calendar)) {
1123 return calendar;
1124 } else if (clazz.isAssignableFrom(Literal.class)) {
1125 return VALUE_FACTORY.createLiteral(calendar);
1126 } else if (clazz.isAssignableFrom(String.class)) {
1127 return calendar.toXMLFormat();
1128 } else if (clazz == Date.class) {
1129 return calendar.toGregorianCalendar().getTime();
1130 } else if (clazz.isAssignableFrom(GregorianCalendar.class)) {
1131 return calendar.toGregorianCalendar();
1132 }
1133 return null;
1134 }
1135
1136 @Nullable
1137 private static Object convertURI(final URI uri, final Class<?> clazz) {
1138 if (clazz.isInstance(uri)) {
1139 return uri;
1140 } else if (clazz.isAssignableFrom(String.class)) {
1141 return uri.stringValue();
1142 } else if (clazz == File.class && uri.stringValue().startsWith("file://")) {
1143 return new File(uri.stringValue().substring(7));
1144 }
1145 return null;
1146 }
1147
1148 @Nullable
1149 private static Object convertBNode(final BNode bnode, final Class<?> clazz) {
1150 if (clazz.isInstance(bnode)) {
1151 return bnode;
1152 } else if (clazz.isAssignableFrom(URI.class)) {
1153 return VALUE_FACTORY.createURI("bnode:" + bnode.getID());
1154 } else if (clazz.isAssignableFrom(String.class)) {
1155 return "_:" + bnode.getID();
1156 }
1157 return null;
1158 }
1159
1160 @Nullable
1161 private static Object convertEnum(final Enum<?> constant, final Class<?> clazz) {
1162 if (clazz.isInstance(constant)) {
1163 return constant;
1164 } else if (clazz.isAssignableFrom(String.class)) {
1165 return constant.name();
1166 } else if (clazz.isAssignableFrom(Literal.class)) {
1167 return VALUE_FACTORY.createLiteral(constant.name(), XMLSchema.STRING);
1168 }
1169 return null;
1170 }
1171
1172 @Nullable
1173 private static Object convertFile(final File file, final Class<?> clazz) {
1174 if (clazz.isInstance(file)) {
1175 return clazz.cast(file);
1176 } else if (clazz.isAssignableFrom(URI.class)) {
1177 return VALUE_FACTORY.createURI("file://" + file.getAbsolutePath());
1178 } else if (clazz.isAssignableFrom(String.class)) {
1179 return file.getAbsolutePath();
1180 }
1181 return null;
1182 }
1183
1184 private static BigDecimal toBigDecimal(final Number number) {
1185 if (number instanceof BigDecimal) {
1186 return (BigDecimal) number;
1187 } else if (number instanceof BigInteger) {
1188 return new BigDecimal((BigInteger) number);
1189 } else if (number instanceof Double || number instanceof Float) {
1190 final double value = number.doubleValue();
1191 return Double.isInfinite(value) || Double.isNaN(value) ? null : new BigDecimal(value);
1192 } else {
1193 return new BigDecimal(number.longValue());
1194 }
1195 }
1196
1197 private static BigInteger toBigInteger(final Number number) {
1198 if (number instanceof BigInteger) {
1199 return (BigInteger) number;
1200 } else if (number instanceof BigDecimal) {
1201 return ((BigDecimal) number).toBigInteger();
1202 } else if (number instanceof Double || number instanceof Float) {
1203 return new BigDecimal(number.doubleValue()).toBigInteger();
1204 } else {
1205 return BigInteger.valueOf(number.longValue());
1206 }
1207 }
1208
1209 private static long toLong(final String string) {
1210 long multiplier = 1;
1211 final char c = string.charAt(string.length() - 1);
1212 if (c == 'k' || c == 'K') {
1213 multiplier = 1024;
1214 } else if (c == 'm' || c == 'M') {
1215 multiplier = 1024 * 1024;
1216 } else if (c == 'g' || c == 'G') {
1217 multiplier = 1024 * 1024 * 1024;
1218 }
1219 return Long.parseLong(multiplier == 1 ? string : string.substring(0, string.length() - 1))
1220 * multiplier;
1221 }
1222
1223 private Statements() {
1224 }
1225
1226 private static final class ValueComparator implements Comparator<Value> {
1227
1228 private final List<String> rankedNamespaces;
1229
1230 public ValueComparator(@Nullable final String... rankedNamespaces) {
1231 this.rankedNamespaces = Arrays.asList(rankedNamespaces);
1232 }
1233
1234 @Override
1235 public int compare(final Value v1, final Value v2) {
1236 if (v1 instanceof URI) {
1237 if (v2 instanceof URI) {
1238 final int rank1 = rankOf(((URI) v1).getNamespace());
1239 final int rank2 = rankOf(((URI) v2).getNamespace());
1240 if (rank1 >= 0 && (rank1 < rank2 || rank2 < 0)) {
1241 return -1;
1242 } else if (rank2 >= 0 && (rank2 < rank1 || rank1 < 0)) {
1243 return 1;
1244 }
1245 final String string1 = Statements.formatValue(v1, Namespaces.DEFAULT);
1246 final String string2 = Statements.formatValue(v2, Namespaces.DEFAULT);
1247 return string1.compareTo(string2);
1248 } else {
1249 return -1;
1250 }
1251 } else if (v1 instanceof BNode) {
1252 if (v2 instanceof BNode) {
1253 return ((BNode) v1).getID().compareTo(((BNode) v2).getID());
1254 } else if (v2 instanceof URI) {
1255 return 1;
1256 } else {
1257 return -1;
1258 }
1259 } else if (v1 instanceof Literal) {
1260 if (v2 instanceof Literal) {
1261 return ((Literal) v1).getLabel().compareTo(((Literal) v2).getLabel());
1262 } else if (v2 instanceof Resource) {
1263 return 1;
1264 } else {
1265 return -1;
1266 }
1267 } else {
1268 if (v1 == v2) {
1269 return 0;
1270 } else {
1271 return 1;
1272 }
1273 }
1274 }
1275
1276 private int rankOf(final String ns) {
1277 for (int i = 0; i < this.rankedNamespaces.size(); ++i) {
1278 if (ns.startsWith(this.rankedNamespaces.get(i))) {
1279 return i;
1280 }
1281 }
1282 return -1;
1283 }
1284
1285 }
1286
1287 private static final class StatementComparator implements Comparator<Statement> {
1288
1289 private final String components;
1290
1291 private final Comparator<? super Value> valueComparator;
1292
1293 public StatementComparator(final String components,
1294 final Comparator<? super Value> valueComparator) {
1295 this.components = components.trim().toLowerCase();
1296 this.valueComparator = Objects.requireNonNull(valueComparator);
1297 for (int i = 0; i < this.components.length(); ++i) {
1298 final char c = this.components.charAt(i);
1299 if (c != 's' && c != 'p' && c != 'o' && c != 'c') {
1300 throw new IllegalArgumentException("Invalid components: " + components);
1301 }
1302 }
1303 }
1304
1305 @Override
1306 public int compare(final Statement s1, final Statement s2) {
1307 for (int i = 0; i < this.components.length(); ++i) {
1308 final char c = this.components.charAt(i);
1309 final Value v1 = getValue(s1, c);
1310 final Value v2 = getValue(s2, c);
1311 final int result = this.valueComparator.compare(v1, v2);
1312 if (result != 0) {
1313 return result;
1314 }
1315 }
1316 return 0;
1317 }
1318
1319 private Value getValue(final Statement statement, final char component) {
1320 switch (component) {
1321 case 's':
1322 return statement.getSubject();
1323 case 'p':
1324 return statement.getPredicate();
1325 case 'o':
1326 return statement.getObject();
1327 case 'c':
1328 return statement.getContext();
1329 default:
1330 throw new Error();
1331 }
1332 }
1333
1334 }
1335
1336 private static final class StatementMatcher implements Predicate<Statement> {
1337
1338 @Nullable
1339 private final ValueMatcher subjMatcher;
1340
1341 @Nullable
1342 private final ValueMatcher predMatcher;
1343
1344 @Nullable
1345 private final ValueMatcher objMatcher;
1346
1347 @Nullable
1348 private final ValueMatcher ctxMatcher;
1349
1350 @SuppressWarnings("unchecked")
1351 public StatementMatcher(final String spec) {
1352
1353
1354 final List<?>[] expressions = new List<?>[4];
1355 final Boolean[] includes = new Boolean[4];
1356 for (int i = 0; i < 4; ++i) {
1357 expressions[i] = new ArrayList<String>();
1358 }
1359
1360
1361 char action = 0;
1362 final List<Integer> components = new ArrayList<Integer>();
1363 for (final String token : spec.split("\\s+")) {
1364 final char ch0 = token.charAt(0);
1365 if (ch0 == '+' || ch0 == '-') {
1366 action = ch0;
1367 if (token.length() == 1) {
1368 throw new IllegalArgumentException("No component(s) specified in '" + spec
1369 + "'");
1370 }
1371 components.clear();
1372 for (int i = 1; i < token.length(); ++i) {
1373 final char ch1 = Character.toLowerCase(token.charAt(i));
1374 final int component = ch1 == 's' ? 0 : ch1 == 'p' ? 1 : ch1 == 'o' ? 2
1375 : ch1 == 'c' ? 3 : -1;
1376 if (component < 0) {
1377 throw new IllegalArgumentException("Invalid component '" + ch1
1378 + "' in '" + spec + "'");
1379 }
1380 components.add(component);
1381 }
1382 } else if (action == 0) {
1383 throw new IllegalArgumentException("Missing selector in '" + spec + "'");
1384 } else {
1385 for (final int component : components) {
1386 ((List<String>) expressions[component]).add(token);
1387 final Boolean include = action == '+' ? Boolean.TRUE : Boolean.FALSE;
1388 if (includes[component] != null
1389 && !Objects.equals(includes[component], include)) {
1390 throw new IllegalArgumentException(
1391 "Include (+) and exclude (-) rules both "
1392 + "specified for same component in '" + spec + "'");
1393 }
1394 includes[component] = include;
1395 }
1396 }
1397 }
1398
1399
1400 final ValueMatcher[] matchers = new ValueMatcher[4];
1401 for (int i = 0; i < 4; ++i) {
1402 matchers[i] = expressions[i].isEmpty() ? null : new ValueMatcher(
1403 (List<String>) expressions[i], Boolean.TRUE.equals(includes[i]));
1404 }
1405 this.subjMatcher = matchers[0];
1406 this.predMatcher = matchers[1];
1407 this.objMatcher = matchers[2];
1408 this.ctxMatcher = matchers[3];
1409 }
1410
1411 @Override
1412 public boolean test(final Statement stmt) {
1413 return (this.subjMatcher == null || this.subjMatcher.match(stmt.getSubject()))
1414 && (this.predMatcher == null || this.predMatcher.match(stmt.getPredicate()))
1415 && (this.objMatcher == null || this.objMatcher.match(stmt.getObject()))
1416 && (this.ctxMatcher == null || this.ctxMatcher.match(stmt.getContext()));
1417 }
1418
1419 private static final class ValueMatcher {
1420
1421 private final boolean include;
1422
1423
1424
1425 private final boolean matchAnyURI;
1426
1427 private final Set<String> matchedURINamespaces;
1428
1429 private final Set<URI> matchedURIs;
1430
1431
1432
1433 private final boolean matchAnyBNode;
1434
1435 private final Set<BNode> matchedBNodes;
1436
1437
1438
1439 private final boolean matchAnyPlainLiteral;
1440
1441 private final boolean matchAnyLangLiteral;
1442
1443 private final boolean matchAnyTypedLiteral;
1444
1445 private final Set<String> matchedLanguages;
1446
1447 private final Set<URI> matchedDatatypeURIs;
1448
1449 private final Set<String> matchedDatatypeNamespaces;
1450
1451 private final Set<Literal> matchedLiterals;
1452
1453 ValueMatcher(final Iterable<String> matchExpressions, final boolean include) {
1454
1455 this.include = include;
1456
1457 this.matchedURINamespaces = new HashSet<>();
1458 this.matchedURIs = new HashSet<>();
1459 this.matchedBNodes = new HashSet<>();
1460 this.matchedLanguages = new HashSet<>();
1461 this.matchedDatatypeURIs = new HashSet<>();
1462 this.matchedDatatypeNamespaces = new HashSet<>();
1463 this.matchedLiterals = new HashSet<>();
1464
1465 boolean matchAnyURI = false;
1466 boolean matchAnyBNode = false;
1467 boolean matchAnyPlainLiteral = false;
1468 boolean matchAnyLangLiteral = false;
1469 boolean matchAnyTypedLiteral = false;
1470
1471 for (final String expression : matchExpressions) {
1472 if ("<*>".equals(expression)) {
1473 matchAnyURI = true;
1474 } else if ("_:*".equals(expression)) {
1475 matchAnyBNode = true;
1476 } else if ("*".equals(expression)) {
1477 matchAnyPlainLiteral = true;
1478 } else if ("*@*".equals(expression)) {
1479 matchAnyLangLiteral = true;
1480 } else if ("*^^*".equals(expression)) {
1481 matchAnyTypedLiteral = true;
1482 } else if (expression.startsWith("*@")) {
1483 this.matchedLanguages.add(expression.substring(2));
1484 } else if (expression.startsWith("*^^")) {
1485 if (expression.endsWith(":*")) {
1486 this.matchedDatatypeNamespaces.add(Namespaces.DEFAULT
1487 .uriFor(expression.substring(3, expression.length() - 2)));
1488 } else {
1489 this.matchedDatatypeURIs.add((URI) Statements.parseValue(
1490 expression.substring(3), Namespaces.DEFAULT));
1491 }
1492 } else if (expression.endsWith(":*")) {
1493 this.matchedURINamespaces.add(Namespaces.DEFAULT.uriFor(expression
1494 .substring(0, expression.length() - 2)));
1495
1496 } else if (expression.endsWith("*>")) {
1497 this.matchedURINamespaces.add(expression.substring(1,
1498 expression.length() - 2));
1499 } else {
1500 final Value value = Statements.parseValue(expression, Namespaces.DEFAULT);
1501 if (value instanceof URI) {
1502 this.matchedURIs.add((URI) value);
1503 } else if (value instanceof BNode) {
1504 this.matchedBNodes.add((BNode) value);
1505 } else if (value instanceof Literal) {
1506 this.matchedLiterals.add((Literal) value);
1507 }
1508
1509 }
1510 }
1511
1512 this.matchAnyURI = matchAnyURI;
1513 this.matchAnyBNode = matchAnyBNode;
1514 this.matchAnyPlainLiteral = matchAnyPlainLiteral;
1515 this.matchAnyLangLiteral = matchAnyLangLiteral;
1516 this.matchAnyTypedLiteral = matchAnyTypedLiteral;
1517 }
1518
1519 boolean match(final Value value) {
1520 final boolean matched = matchHelper(value);
1521 return this.include == matched;
1522 }
1523
1524 private boolean matchHelper(final Value value) {
1525 if (value instanceof URI) {
1526 return this.matchAnyURI
1527 || contains(this.matchedURIs, value)
1528 || containsNs(this.matchedURINamespaces, (URI) value);
1529 } else if (value instanceof Literal) {
1530 final Literal lit = (Literal) value;
1531 final String lang = lit.getLanguage();
1532 final URI dt = lit.getDatatype();
1533 return lang == null
1534 && (dt == null || XMLSchema.STRING.equals(dt))
1535 && this.matchAnyPlainLiteral
1536 || lang != null
1537 && (this.matchAnyLangLiteral || contains(this.matchedLanguages, lang))
1538 || dt != null
1539 && (this.matchAnyTypedLiteral
1540 || contains(this.matchedDatatypeURIs, dt) || containsNs(
1541 this.matchedDatatypeNamespaces, dt))
1542 || contains(this.matchedLiterals, lit);
1543 } else {
1544 return this.matchAnyBNode
1545 || contains(this.matchedBNodes, value);
1546 }
1547 }
1548
1549 private static boolean contains(final Set<?> set, final Object value) {
1550 return !set.isEmpty() && set.contains(value);
1551 }
1552
1553 private static boolean containsNs(final Set<String> set, final URI uri) {
1554 if (set.isEmpty()) {
1555 return false;
1556 }
1557 if (set.contains(uri.getNamespace())) {
1558 return true;
1559 }
1560 final String uriString = uri.stringValue();
1561 for (final String elem : set) {
1562 if (uriString.startsWith(elem)) {
1563 return true;
1564 }
1565 }
1566 return false;
1567 }
1568
1569 }
1570
1571 }
1572
1573 }