1
2
3
4
5
6
7
8
9
10
11
12
13
14 package eu.fbk.rdfpro.util;
15
16 import java.io.IOException;
17 import java.io.ObjectOutputStream;
18 import java.lang.ref.Reference;
19 import java.lang.ref.SoftReference;
20 import java.math.BigDecimal;
21 import java.math.BigInteger;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.NoSuchElementException;
29 import java.util.Objects;
30 import java.util.Set;
31
32 import javax.annotation.Nullable;
33 import javax.xml.datatype.XMLGregorianCalendar;
34
35 import org.openrdf.model.BNode;
36 import org.openrdf.model.Literal;
37 import org.openrdf.model.Namespace;
38 import org.openrdf.model.Resource;
39 import org.openrdf.model.Statement;
40 import org.openrdf.model.URI;
41 import org.openrdf.model.Value;
42 import org.openrdf.model.datatypes.XMLDatatypeUtil;
43 import org.openrdf.model.impl.NamespaceImpl;
44 import org.openrdf.model.util.URIUtil;
45 import org.openrdf.model.vocabulary.RDF;
46 import org.openrdf.model.vocabulary.SESAME;
47 import org.openrdf.model.vocabulary.XMLSchema;
48
49 final class QuadModelImpl extends QuadModel {
50
51 private static final int INITIAL_VALUE_TABLE_SIZE = 256 - 1;
52
53 private static final int INITIAL_STATEMENT_TABLE_SIZE = 256 - 1;
54
55 private static final ModelURI NULL_VALUE = new ModelURI(null, "sesame:null");
56
57 private static final ModelStatement NULL_STATEMENT = new ModelStatement(NULL_VALUE,
58 NULL_VALUE, NULL_VALUE, NULL_VALUE);
59
60 private static final int SUBJ = 0;
61
62 private static final int PRED = 1;
63
64 private static final int OBJ = 2;
65
66 private static final int CTX = 3;
67
68 private static final long serialVersionUID = 1L;
69
70 private final Map<String, Namespace> namespaces;
71
72 private final StringIndex stringIndex;
73
74 private ModelValue[] valueTable;
75
76 private int valueCount;
77
78 private int valueSlots;
79
80 private final ModelURI valueNil;
81
82 private final ModelURI valueLang;
83
84 private ModelStatement[] statementTable;
85
86 private int statementSlots;
87
88 private int statementCount;
89
90 private int statementZombies;
91
92 public QuadModelImpl() {
93 this.namespaces = new HashMap<>();
94 this.stringIndex = new StringIndex();
95 this.valueTable = new ModelValue[INITIAL_VALUE_TABLE_SIZE];
96 this.valueCount = 0;
97 this.valueSlots = 0;
98 this.statementTable = new ModelStatement[INITIAL_STATEMENT_TABLE_SIZE];
99 this.statementCount = 0;
100 this.statementSlots = 0;
101 this.statementZombies = 0;
102 this.valueNil = (ModelURI) lookupValue(SESAME.NIL, true);
103 this.valueLang = (ModelURI) lookupValue(RDF.LANGSTRING, true);
104 }
105
106
107
108 @Override
109 protected Set<Namespace> doGetNamespaces() {
110 return new HashSet<>(this.namespaces.values());
111 }
112
113 @Override
114 protected Namespace doGetNamespace(final String prefix) {
115 return this.namespaces.get(prefix);
116
117 }
118
119 @Override
120 protected Namespace doSetNamespace(final String prefix, @Nullable final String name) {
121 if (name == null) {
122 return this.namespaces.remove(prefix);
123 } else {
124 return this.namespaces.put(prefix, new NamespaceImpl(prefix, name));
125 }
126 }
127
128
129
130
131
132
133
134
135 @Override
136 protected int doSize(@Nullable final Resource subj, @Nullable final URI pred,
137 @Nullable final Value obj, final Resource[] ctxs) {
138
139
140 Objects.requireNonNull(ctxs);
141
142
143 final ModelResource msubj = (ModelResource) lookupValue(subj, false);
144 final ModelURI mpred = (ModelURI) lookupValue(pred, false);
145 final ModelValue mobj = lookupValue(obj, false);
146
147
148 if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE) {
149 return 0;
150 }
151
152
153 if (ctxs.length == 0) {
154
155 return doSize(msubj, mpred, mobj, (ModelResource) null);
156
157 } else {
158
159 int size = 0;
160 for (final Resource ctx : ctxs) {
161 final ModelResource mctx = ctx == null ? this.valueNil
162 : (ModelResource) lookupValue(ctx, false);
163 size += mctx == NULL_VALUE ? 0 : doSize(msubj, mpred, mobj, mctx);
164 }
165 return size;
166 }
167 }
168
169 @Override
170 protected int doSizeEstimate(@Nullable final Resource subj, @Nullable final URI pred,
171 @Nullable final Value obj, @Nullable final Resource ctx) {
172
173
174 final ModelResource msubj = (ModelResource) lookupValue(subj, false);
175 final ModelURI mpred = (ModelURI) lookupValue(pred, false);
176 final ModelValue mobj = lookupValue(obj, false);
177 final ModelResource mctx = (ModelResource) lookupValue(ctx, false);
178
179
180 if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE || mctx == NULL_VALUE) {
181 return 0;
182 }
183
184
185 return doSizeEstimate(msubj, mpred, mobj, mctx);
186 }
187
188 @Override
189 protected Iterator<Statement> doIterator(@Nullable final Resource subj,
190 @Nullable final URI pred, @Nullable final Value obj, final Resource[] ctxs) {
191
192
193 Objects.requireNonNull(ctxs);
194
195
196 final ModelResource msubj = (ModelResource) lookupValue(subj, false);
197 final ModelURI mpred = (ModelURI) lookupValue(pred, false);
198 final ModelValue mobj = lookupValue(obj, false);
199
200
201 if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE) {
202 return Collections.emptyIterator();
203 }
204
205
206 if (ctxs.length == 0) {
207
208 return doIterator(msubj, mpred, mobj, (ModelResource) null);
209
210 } else if (ctxs.length == 1) {
211
212 final ModelResource mctx = ctxs[0] == null ? this.valueNil
213 : (ModelResource) lookupValue(ctxs[0], false);
214 return mctx == NULL_VALUE ? Collections.emptyIterator()
215 : doIterator(msubj, mpred, mobj, mctx);
216
217 } else {
218
219 final Iterator<Resource> ctxIterator = Arrays.asList(ctxs).iterator();
220 return Iterators.concat(Iterators.transform(ctxIterator, (final Resource ctx) -> {
221 final ModelResource mctx = ctx == null ? this.valueNil
222 : (ModelResource) lookupValue(ctx, false);
223 return ctx == NULL_VALUE ? Collections.emptyIterator()
224 : doIterator(msubj, mpred, mobj, mctx);
225 }));
226 }
227 }
228
229 @Override
230 protected boolean doAdd(final Resource subj, final URI pred, final Value obj,
231 final Resource[] ctxs) {
232
233
234 Objects.requireNonNull(subj);
235 Objects.requireNonNull(pred);
236 Objects.requireNonNull(obj);
237 Objects.requireNonNull(ctxs);
238
239
240 final ModelResource msubj = (ModelResource) lookupValue(subj, true);
241 final ModelURI mpred = (ModelURI) lookupValue(pred, true);
242 final ModelValue mobj = lookupValue(obj, true);
243
244
245 if (ctxs.length == 0) {
246
247 return doAdd(msubj, mpred, mobj, this.valueNil);
248
249 } else {
250
251 boolean modified = false;
252 for (final Resource ctx : ctxs) {
253 final ModelResource mctx = ctx == null ? this.valueNil
254 : (ModelResource) lookupValue(ctx, true);
255 final boolean isNew = doAdd(msubj, mpred, mobj, mctx);
256 modified |= isNew;
257 }
258 return modified;
259 }
260 }
261
262 @Override
263 protected boolean doRemove(@Nullable final Resource subj, @Nullable final URI pred,
264 @Nullable final Value obj, final Resource[] ctxs) {
265
266
267 Objects.requireNonNull(ctxs);
268
269
270 final ModelResource msubj = (ModelResource) lookupValue(subj, false);
271 final ModelURI mpred = (ModelURI) lookupValue(pred, false);
272 final ModelValue mobj = lookupValue(obj, false);
273
274
275 if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE) {
276 return false;
277 }
278
279
280 if (ctxs.length == 0) {
281
282 return doRemove(msubj, mpred, mobj, (ModelResource) null);
283
284 } else {
285
286 boolean modified = false;
287 for (final Resource ctx : ctxs) {
288 final ModelResource mctx = ctx == null ? this.valueNil
289 : (ModelResource) lookupValue(ctx, false);
290 if (mctx != NULL_VALUE) {
291 final boolean m = doRemove(msubj, mpred, mobj, mctx);
292 modified |= m;
293 }
294 }
295 return modified;
296 }
297 }
298
299 @Override
300 protected synchronized Value doNormalize(final Value value) {
301 return lookupValue(value, true);
302 }
303
304
305
306 private int doSize(@Nullable final ModelResource subj, @Nullable final ModelURI pred,
307 @Nullable final ModelValue obj, @Nullable final ModelResource ctx) {
308
309
310 final int comp = selectComponent(subj, pred, obj, ctx);
311
312
313 if (comp < 0) {
314 return this.statementCount;
315 }
316
317
318
319 int size = 0;
320 final ModelValue value = comp == 0 ? subj : comp == 1 ? pred : comp == 2 ? obj : ctx;
321 for (ModelStatement stmt = value.next(comp); stmt != null; stmt = stmt.next(comp)) {
322 if (stmt.match(subj, pred, obj, ctx)) {
323 ++size;
324 }
325 }
326 return size;
327 }
328
329 private int doSizeEstimate(@Nullable final ModelResource subj, @Nullable final ModelURI pred,
330 @Nullable final ModelValue obj, @Nullable final ModelResource ctx) {
331
332 int size = this.statementCount;
333 if (subj != null && subj.numSubj < size) {
334 size = subj.numSubj;
335 }
336 if (pred != null && pred.numPred < size) {
337 size = pred.numPred;
338 }
339 if (obj != null && obj.numObj < size) {
340 size = obj.numObj;
341 }
342 if (ctx != null && ctx.numCtx < size) {
343 size = ctx.numCtx;
344 }
345 return size;
346 }
347
348 private Iterator<Statement> doIterator(@Nullable final ModelResource subj,
349 @Nullable final ModelURI pred, @Nullable final ModelValue obj,
350 @Nullable final ModelResource ctx) {
351
352
353 final int comp = selectComponent(subj, pred, obj, ctx);
354
355
356
357 if (comp < 0) {
358 return new Iterator<Statement>() {
359
360 private int index = 0;
361
362 private ModelStatement next = null;
363
364 private ModelStatement last = null;
365
366 @Override
367 public boolean hasNext() {
368 if (this.next != null) {
369 return true;
370 }
371 while (this.index < QuadModelImpl.this.statementTable.length) {
372 final ModelStatement stmt = QuadModelImpl.this.statementTable[this.index++];
373 if (stmt != null && stmt != NULL_STATEMENT) {
374 this.next = stmt;
375 return true;
376 }
377 }
378 return false;
379 }
380
381 @Override
382 public Statement next() {
383 if (!hasNext()) {
384 throw new NoSuchElementException();
385 }
386 this.last = this.next;
387 this.next = null;
388 return this.last;
389 }
390
391 @Override
392 public void remove() {
393 if (this.last == null) {
394 throw new NoSuchElementException();
395 }
396 removeStatement(this.last.subj, this.last.pred, this.last.obj, this.last.ctx);
397 this.last = null;
398 }
399
400 };
401 }
402
403
404
405
406 final ModelValue value = comp == 0 ? subj : comp == 1 ? pred : comp == 2 ? obj : ctx;
407 ModelStatement stmt = value.next(comp);
408 while (true) {
409 if (stmt == null) {
410 return Collections.emptyIterator();
411 } else if (!stmt.isZombie() && stmt.match(subj, pred, obj, ctx)) {
412 break;
413 }
414 stmt = stmt.next(comp);
415 }
416 final ModelStatement firstStmt = stmt;
417 return new Iterator<Statement>() {
418
419 private ModelStatement next = firstStmt;
420
421 private ModelStatement last = null;
422
423 @Override
424 public boolean hasNext() {
425 return this.next != null;
426 }
427
428 @Override
429 public ModelStatement next() {
430 this.last = this.next;
431 while (true) {
432 this.next = this.next.next(comp);
433 if (this.next == null || !this.next.isZombie()
434 && this.next.match(subj, pred, obj, ctx)) {
435 break;
436 }
437 }
438 return this.last;
439 }
440
441 @Override
442 public void remove() {
443 if (this.last == null) {
444 throw new NoSuchElementException();
445 }
446 removeStatement(this.last.subj, this.last.pred, this.last.obj, this.last.ctx);
447 this.last = null;
448 }
449
450 };
451 }
452
453 private boolean doAdd(final ModelResource subj, final ModelURI pred, final ModelValue obj,
454 final ModelResource ctx) {
455
456
457 final int hash = ModelStatement.hash(subj, pred, obj, ctx);
458 int slot = (hash & 0x7FFFFFFF) % this.statementTable.length;
459
460
461
462 while (true) {
463
464
465 ModelStatement stmt = this.statementTable[slot];
466 final boolean isNull = stmt == null;
467 if (isNull || stmt == NULL_STATEMENT) {
468
469
470
471 stmt = new ModelStatement(subj, pred, obj, ctx);
472 this.statementTable[slot] = stmt;
473 ++this.statementCount;
474 if (isNull) {
475 ++this.statementSlots;
476 if (this.statementSlots * 2 >= this.statementTable.length) {
477 rehashStatements();
478 }
479 }
480
481
482 stmt.nextBySubj = subj.nextBySubj;
483 stmt.nextByPred = pred.nextByPred;
484 stmt.nextByObj = obj.nextByObj;
485 stmt.nextByCtx = ctx.nextByCtx;
486 subj.nextBySubj = stmt;
487 pred.nextByPred = stmt;
488 obj.nextByObj = stmt;
489 ctx.nextByCtx = stmt;
490
491
492 ++subj.numSubj;
493 ++pred.numPred;
494 ++obj.numObj;
495 ++ctx.numCtx;
496
497
498 return true;
499
500 } else if (subj == stmt.subj && pred == stmt.pred && obj == stmt.obj
501 && ctx == stmt.ctx) {
502
503
504 return false;
505
506 } else {
507
508
509 slot = incrementSlot(slot, this.statementTable.length);
510
511 }
512 }
513 }
514
515 private boolean doRemove(@Nullable final ModelResource subj, @Nullable final ModelURI pred,
516 @Nullable final ModelValue obj, @Nullable final ModelResource ctx) {
517
518
519 if (this.statementCount == 0) {
520 return false;
521 }
522
523
524 if (subj != null && pred != null && obj != null && ctx != null) {
525 return removeStatement(subj, pred, obj, ctx);
526 }
527
528
529 final int comp = selectComponent(subj, pred, obj, ctx);
530
531
532 if (comp < 0) {
533 this.statementTable = new ModelStatement[INITIAL_STATEMENT_TABLE_SIZE];
534 this.statementCount = 0;
535 this.statementSlots = 0;
536 for (final ModelValue value : this.valueTable) {
537 if (value != null) {
538 value.nextByObj = null;
539 value.numObj = 0;
540 if (value instanceof ModelResource) {
541 final ModelResource resource = (ModelResource) value;
542 resource.nextBySubj = null;
543 resource.numSubj = 0;
544 resource.nextByCtx = null;
545 resource.numCtx = 0;
546 if (value instanceof ModelURI) {
547 final ModelURI uri = (ModelURI) value;
548 uri.nextByPred = null;
549 uri.numPred = 0;
550 }
551 }
552 }
553 }
554 return true;
555 }
556
557
558 boolean modified = false;
559 final ModelValue value = comp == 0 ? subj : comp == 1 ? pred : comp == 2 ? obj : ctx;
560 ModelStatement stmt = value.next(comp);
561 while (stmt != null) {
562 final ModelStatement next = stmt.next(comp);
563 if (stmt.match(subj, pred, obj, ctx)) {
564 final boolean m = removeStatement(stmt.subj, stmt.pred, stmt.obj, stmt.ctx);
565 modified |= m;
566 }
567 stmt = next;
568 }
569 return modified;
570 }
571
572
573
574 private boolean removeStatement(final ModelResource subj, final ModelURI pred,
575 final ModelValue obj, final ModelResource ctx) {
576
577
578 final int hash = ModelStatement.hash(subj, pred, obj, ctx);
579 int slot = (hash & 0x7FFFFFFF) % this.statementTable.length;
580 ModelStatement mstmt;
581 while (true) {
582 mstmt = this.statementTable[slot];
583 if (mstmt == null) {
584 return false;
585 } else if (mstmt != NULL_STATEMENT && subj == mstmt.subj && pred == mstmt.pred
586 && obj == mstmt.obj && ctx == mstmt.ctx) {
587 this.statementTable[slot] = NULL_STATEMENT;
588 break;
589 } else {
590 slot = incrementSlot(slot, this.statementTable.length);
591 }
592 }
593
594
595 mstmt.markZombie();
596
597
598 --subj.numSubj;
599 --pred.numPred;
600 --obj.numObj;
601 --ctx.numCtx;
602 --this.statementCount;
603 ++this.statementZombies;
604
605
606 if (this.statementZombies >= this.statementCount) {
607 cleanZombies();
608 }
609
610
611 return true;
612 }
613
614 private ModelValue lookupValue(@Nullable final Value value, final boolean canCreate) {
615
616
617 if (value == null) {
618 return null;
619 }
620
621
622 if (value instanceof ModelValue) {
623 final ModelValue mv = (ModelValue) value;
624 if (mv.model == this) {
625 return mv;
626 }
627 }
628
629
630 final int hash = value.hashCode();
631 int slot = (hash & 0x7FFFFFFF) % this.valueTable.length;
632 while (true) {
633 ModelValue mv = this.valueTable[slot];
634 final boolean isNull = mv == null;
635 if (isNull || mv == NULL_VALUE) {
636
637
638 if (!canCreate) {
639 return NULL_VALUE;
640 }
641
642
643 if (value instanceof URI) {
644 mv = new ModelURI(this, value.stringValue());
645 } else if (value instanceof BNode) {
646 mv = new ModelBNode(this, ((BNode) value).getID());
647 } else if (value instanceof Literal) {
648 final Literal lit = (Literal) value;
649 final String language = lit.getLanguage();
650 final URI datatype = lit.getLanguage() != null ? RDF.LANGSTRING
651 : lit.getDatatype() != null ? lit.getDatatype() : XMLSchema.STRING;
652 mv = new ModelLiteral(this, lit.getLabel(), language == null ? null
653 : language.intern(), (ModelURI) lookupValue(datatype, true));
654 } else {
655 throw new Error(value.getClass().getName());
656 }
657
658
659 this.valueTable[slot] = mv;
660 ++this.valueCount;
661 if (isNull) {
662 ++this.valueSlots;
663 if (this.valueSlots * 2 >= this.valueTable.length) {
664 rehashValues();
665 }
666 }
667
668
669 return mv;
670
671 } else if (mv.equals(value)) {
672
673
674 return mv;
675
676 } else {
677
678
679 slot = incrementSlot(slot, this.valueTable.length);
680
681 }
682 }
683 }
684
685 private void rehashValues() {
686
687
688 if (this.valueSlots < this.valueTable.length / 2) {
689 return;
690 }
691
692
693
694 final int newLength = this.valueTable.length
695 * (this.valueCount * 2 >= this.valueTable.length ? 2 : 1);
696
697
698 final ModelValue[] oldTable = this.valueTable;
699 this.valueTable = new ModelValue[newLength];
700 for (final ModelValue mv : oldTable) {
701 if (mv != null && mv != NULL_VALUE) {
702 final int hash = mv.hashCode();
703 int slot = (hash & 0x7FFFFFFF) % this.valueTable.length;
704 while (this.valueTable[slot] != null) {
705 slot = incrementSlot(slot, this.valueTable.length);
706 }
707 this.valueTable[slot] = mv;
708 }
709 }
710
711
712 this.valueSlots = this.valueCount;
713 }
714
715 private void rehashStatements() {
716
717
718 if (this.statementSlots * 2 < this.statementTable.length) {
719 return;
720 }
721
722
723
724 final int newLength = this.statementTable.length
725 * (this.statementCount * 2 >= this.statementTable.length ? 2 : 1);
726
727
728 final ModelStatement[] oldTable = this.statementTable;
729 this.statementTable = new ModelStatement[newLength];
730 for (final ModelStatement mstmt : oldTable) {
731 if (mstmt != null && mstmt != NULL_STATEMENT) {
732 final int hash = mstmt.hash();
733 int slot = (hash & 0x7FFFFFFF) % this.statementTable.length;
734 while (this.statementTable[slot] != null) {
735 slot = incrementSlot(slot, this.statementTable.length);
736 }
737 this.statementTable[slot] = mstmt;
738 }
739 }
740
741
742 this.statementSlots = this.statementCount;
743 }
744
745 private void cleanZombies() {
746
747
748 ModelStatement stmt;
749 ModelStatement prev;
750 for (final ModelValue mv : this.valueTable) {
751
752
753 if (mv == null || mv == NULL_VALUE) {
754 continue;
755 }
756
757
758 for (prev = null, stmt = mv.nextByObj; stmt != null; stmt = stmt.nextByObj) {
759 if (!stmt.isZombie()) {
760 prev = stmt;
761 } else if (prev == null) {
762 mv.nextByObj = stmt.nextByObj;
763 } else {
764 prev.nextByObj = stmt.nextByObj;
765 }
766 }
767
768
769 if (!(mv instanceof ModelResource)) {
770 continue;
771 }
772 final ModelResource mr = (ModelResource) mv;
773
774
775 for (prev = null, stmt = mr.nextBySubj; stmt != null; stmt = stmt.nextBySubj) {
776 if (!stmt.isZombie()) {
777 prev = stmt;
778 } else if (prev == null) {
779 mr.nextBySubj = stmt.nextBySubj;
780 } else {
781 prev.nextBySubj = stmt.nextBySubj;
782 }
783 }
784
785
786 for (prev = null, stmt = mr.nextByCtx; stmt != null; stmt = stmt.nextByCtx) {
787 if (!stmt.isZombie()) {
788 prev = stmt;
789 } else if (prev == null) {
790 mr.nextByCtx = stmt.nextByCtx;
791 } else {
792 prev.nextByCtx = stmt.nextByCtx;
793 }
794 }
795
796
797 if (!(mv instanceof ModelURI)) {
798 continue;
799 }
800 final ModelURI mu = (ModelURI) mv;
801
802
803 for (prev = null, stmt = mu.nextByPred; stmt != null; stmt = stmt.nextByPred) {
804 if (!stmt.isZombie()) {
805 prev = stmt;
806 } else if (prev == null) {
807 mu.nextByPred = stmt.nextByPred;
808 } else {
809 prev.nextByPred = stmt.nextByPred;
810 }
811 }
812 }
813
814
815 this.statementZombies = 0;
816 }
817
818 private static int selectComponent(@Nullable final ModelResource subj,
819 @Nullable final ModelURI pred, @Nullable final ModelValue obj,
820 @Nullable final ModelResource ctx) {
821
822
823 int result = -1;
824 int num = Integer.MAX_VALUE;
825
826
827 if (subj != null && subj.numSubj < num) {
828 result = SUBJ;
829 num = subj.numSubj;
830 }
831 if (pred != null && pred.numPred < num) {
832 result = PRED;
833 num = pred.numPred;
834 }
835 if (obj != null && obj.numObj < num) {
836 result = OBJ;
837 num = obj.numObj;
838 }
839 if (ctx != null && ctx.numCtx < num) {
840 result = CTX;
841 num = ctx.numCtx;
842 }
843
844
845 return result;
846 }
847
848 private static int incrementSlot(final int num, final int max) {
849 final int result = num + 1;
850 return result >= max ? 0 : result;
851 }
852
853 private static abstract class ModelValue implements Value, Hashable {
854
855 private static final long serialVersionUID = 1L;
856
857 @Nullable
858 final transient QuadModelImpl model;
859
860 @Nullable
861 transient ModelStatement nextByObj;
862
863 transient int numObj;
864
865 transient long hashLo;
866
867 transient long hashHi;
868
869 ModelValue(final QuadModelImpl model) {
870 this.model = model;
871 }
872
873 ModelStatement next(final int component) {
874 switch (component) {
875 case OBJ:
876 return this.nextByObj;
877 default:
878 return null;
879 }
880 }
881
882 @Override
883 public Hash getHash() {
884 Hash hash;
885 if (this.hashLo != 0L) {
886 hash = Hash.fromLongs(this.hashHi, this.hashLo);
887 } else {
888 hash = Statements.computeHash(this);
889 this.hashHi = hash.getHigh();
890 this.hashLo = hash.getLow();
891 }
892 return hash;
893 }
894
895 }
896
897 private static abstract class ModelResource extends ModelValue implements Resource {
898
899 private static final long serialVersionUID = 1L;
900
901 @Nullable
902 transient ModelStatement nextBySubj;
903
904 @Nullable
905 transient ModelStatement nextByCtx;
906
907 transient int numSubj;
908
909 transient int numCtx;
910
911 ModelResource(final QuadModelImpl model) {
912 super(model);
913 }
914
915 @Override
916 ModelStatement next(final int component) {
917 switch (component) {
918 case SUBJ:
919 return this.nextBySubj;
920 case OBJ:
921 return this.nextByObj;
922 case CTX:
923 return this.nextByCtx;
924 default:
925 return null;
926 }
927 }
928
929 }
930
931 private static final class ModelURI extends ModelResource implements URI {
932
933 private static final long serialVersionUID = 1L;
934
935 private transient final int namespace;
936
937 private transient final int localName;
938
939 private final int hash;
940
941 private Object cachedString;
942
943 @Nullable
944 transient ModelStatement nextByPred;
945
946 transient int numPred;
947
948 ModelURI(@Nullable final QuadModelImpl model, final String string) {
949 super(model);
950 final int index = URIUtil.getLocalNameIndex(string);
951 if (model != null) {
952 this.namespace = model.stringIndex.put(string.substring(0, index));
953 this.localName = model.stringIndex.put(string.substring(index));
954 this.cachedString = null;
955 } else {
956 this.namespace = 0;
957 this.localName = 0;
958 this.cachedString = string;
959 }
960 this.hash = string.hashCode();
961 }
962
963 @Override
964 ModelStatement next(final int component) {
965 switch (component) {
966 case SUBJ:
967 return this.nextBySubj;
968 case PRED:
969 return this.nextByPred;
970 case OBJ:
971 return this.nextByObj;
972 case CTX:
973 return this.nextByCtx;
974 default:
975 throw new Error();
976 }
977 }
978
979 @Nullable
980 private String getCachedString(final boolean compute) {
981 if (this.cachedString instanceof Reference<?>) {
982 @SuppressWarnings("unchecked")
983 final String s = ((Reference<String>) this.cachedString).get();
984 if (s == null) {
985 this.cachedString = null;
986 } else {
987 return s;
988 }
989 } else if (this.cachedString instanceof String) {
990 return (String) this.cachedString;
991 }
992 if (compute) {
993 final StringBuilder builder = new StringBuilder();
994 this.model.stringIndex.get(this.namespace, builder);
995 this.model.stringIndex.get(this.localName, builder);
996 final String s = builder.toString();
997 this.cachedString = new SoftReference<String>(s);
998 return s;
999 }
1000 return null;
1001 }
1002
1003 private void writeObject(final ObjectOutputStream out) throws IOException {
1004 final String string = getCachedString(true);
1005 final Object oldCachedString = this.cachedString;
1006 this.cachedString = string;
1007 out.defaultWriteObject();
1008 this.cachedString = oldCachedString;
1009 }
1010
1011 @Override
1012 public String getNamespace() {
1013 if (this.model != null) {
1014 return this.model.stringIndex.get(this.namespace);
1015 } else {
1016 final String s = (String) this.cachedString;
1017 final int index = URIUtil.getLocalNameIndex(s);
1018 return s.substring(0, index);
1019 }
1020 }
1021
1022 @Override
1023 public String getLocalName() {
1024 if (this.model != null) {
1025 return this.model.stringIndex.get(this.localName);
1026 } else {
1027 final String s = (String) this.cachedString;
1028 final int index = URIUtil.getLocalNameIndex(s);
1029 return s.substring(index);
1030 }
1031 }
1032
1033 @Override
1034 public String stringValue() {
1035 return getCachedString(true);
1036 }
1037
1038 @Override
1039 public boolean equals(final Object object) {
1040 if (object == this) {
1041 return true;
1042 }
1043 if (this.model != null && object instanceof ModelURI
1044 && this.model == ((ModelURI) object).model) {
1045 return false;
1046 }
1047 if (object instanceof URI) {
1048 final String string = ((URI) object).stringValue();
1049 final String s = getCachedString(false);
1050 if (s != null) {
1051 return s.equals(string);
1052 } else {
1053 final StringIndex index = this.model.stringIndex;
1054 final int namespaceLength = index.length(this.namespace);
1055 final int localNameLength = index.length(this.localName);
1056 return namespaceLength + localNameLength == string.length()
1057 && index.equals(this.namespace, string, 0, namespaceLength)
1058 && index.equals(this.localName, string, namespaceLength,
1059 string.length());
1060 }
1061 }
1062 return false;
1063 }
1064
1065 @Override
1066 public int hashCode() {
1067 return this.hash;
1068 }
1069
1070 @Override
1071 public String toString() {
1072 return stringValue();
1073 }
1074
1075 }
1076
1077 private static final class ModelBNode extends ModelResource implements BNode {
1078
1079 private static final long serialVersionUID = 1L;
1080
1081 private final int id;
1082
1083 private final int hash;
1084
1085 ModelBNode(final QuadModelImpl model, final String id) {
1086 super(model);
1087 this.id = model.stringIndex.put(id);
1088 this.hash = id.hashCode();
1089 }
1090
1091 @Override
1092 public String getID() {
1093 return this.model.stringIndex.get(this.id);
1094 }
1095
1096 @Override
1097 public String stringValue() {
1098 return getID();
1099 }
1100
1101 @Override
1102 public boolean equals(final Object object) {
1103 if (object == this) {
1104 return true;
1105 }
1106 if (this.model != null && object instanceof ModelBNode
1107 && this.model == ((ModelBNode) object).model) {
1108 return false;
1109 }
1110 if (object instanceof BNode) {
1111 return this.model.stringIndex.equals(this.id, ((BNode) object).getID());
1112 }
1113 return false;
1114 }
1115
1116 @Override
1117 public int hashCode() {
1118 return this.hash;
1119 }
1120
1121 @Override
1122 public String toString() {
1123 final StringBuilder builder = new StringBuilder("_:");
1124 this.model.stringIndex.get(this.id, builder);
1125 return builder.toString();
1126 }
1127
1128 }
1129
1130 private static final class ModelLiteral extends ModelValue implements Literal {
1131
1132 private static final long serialVersionUID = 1L;
1133
1134 private final int label;
1135
1136 private final Object langOrDatatype;
1137
1138 private final int hash;
1139
1140 @Nullable
1141 private Object cachedLabel;
1142
1143 ModelLiteral(@Nullable final QuadModelImpl model, final String label,
1144 @Nullable final String language, final ModelURI datatype) {
1145
1146 super(model);
1147
1148 int hashCode = label.hashCode();
1149 if (language != null) {
1150 hashCode = 31 * hashCode + language.hashCode();
1151 }
1152 hashCode = 31 * hashCode + datatype.hashCode();
1153
1154 this.langOrDatatype = language == null ? datatype : language.intern();
1155 this.hash = hashCode;
1156
1157 if (model != null) {
1158 this.label = model.stringIndex.put(label);
1159 this.cachedLabel = null;
1160 } else {
1161 this.label = 0;
1162 this.cachedLabel = label;
1163 }
1164 }
1165
1166 @Nullable
1167 private String getCachedLabel(final boolean compute) {
1168 if (this.cachedLabel instanceof Reference<?>) {
1169 @SuppressWarnings("unchecked")
1170 final String s = ((Reference<String>) this.cachedLabel).get();
1171 if (s == null) {
1172 this.cachedLabel = null;
1173 } else {
1174 return s;
1175 }
1176 } else if (this.cachedLabel instanceof String) {
1177 return (String) this.cachedLabel;
1178 }
1179 if (compute) {
1180 final String s = this.model.stringIndex.get(this.label);
1181 this.cachedLabel = new SoftReference<String>(s);
1182 return s;
1183 }
1184 return null;
1185 }
1186
1187 private void writeObject(final ObjectOutputStream out) throws IOException {
1188 final String label = getCachedLabel(true);
1189 final Object oldCachedLabel = this.cachedLabel;
1190 this.cachedLabel = label;
1191 out.defaultWriteObject();
1192 this.cachedLabel = oldCachedLabel;
1193 }
1194
1195 @Override
1196 public String getLabel() {
1197 return getCachedLabel(true);
1198 }
1199
1200 @Override
1201 public String getLanguage() {
1202 return this.langOrDatatype instanceof String ? (String) this.langOrDatatype : null;
1203 }
1204
1205 @Override
1206 public URI getDatatype() {
1207 return this.langOrDatatype instanceof String ? this.model != null ? this.model.valueLang
1208 : RDF.LANGSTRING
1209 : (URI) this.langOrDatatype;
1210 }
1211
1212 @Override
1213 public String stringValue() {
1214 return getLabel();
1215 }
1216
1217 @Override
1218 public boolean booleanValue() {
1219 return XMLDatatypeUtil.parseBoolean(getLabel());
1220 }
1221
1222 @Override
1223 public byte byteValue() {
1224 return XMLDatatypeUtil.parseByte(getLabel());
1225 }
1226
1227 @Override
1228 public short shortValue() {
1229 return XMLDatatypeUtil.parseShort(getLabel());
1230 }
1231
1232 @Override
1233 public int intValue() {
1234 return XMLDatatypeUtil.parseInt(getLabel());
1235 }
1236
1237 @Override
1238 public long longValue() {
1239 return XMLDatatypeUtil.parseLong(getLabel());
1240 }
1241
1242 @Override
1243 public float floatValue() {
1244 return XMLDatatypeUtil.parseFloat(getLabel());
1245 }
1246
1247 @Override
1248 public double doubleValue() {
1249 return XMLDatatypeUtil.parseDouble(getLabel());
1250 }
1251
1252 @Override
1253 public BigInteger integerValue() {
1254 return XMLDatatypeUtil.parseInteger(getLabel());
1255 }
1256
1257 @Override
1258 public BigDecimal decimalValue() {
1259 return XMLDatatypeUtil.parseDecimal(getLabel());
1260 }
1261
1262 @Override
1263 public XMLGregorianCalendar calendarValue() {
1264 return XMLDatatypeUtil.parseCalendar(getLabel());
1265 }
1266
1267 @Override
1268 public boolean equals(final Object object) {
1269 if (object == this) {
1270 return true;
1271 }
1272 if (this.model != null && object instanceof ModelLiteral
1273 && this.model == ((ModelLiteral) object).model) {
1274 return false;
1275 }
1276 if (object instanceof Literal) {
1277 final Literal l = (Literal) object;
1278 if (this.langOrDatatype instanceof String
1279 && this.langOrDatatype.equals(l.getLanguage())
1280 || this.langOrDatatype instanceof URI
1281 && this.langOrDatatype.equals(l.getDatatype())) {
1282 final String s = getCachedLabel(false);
1283 return s != null ? s.equals(l.getLabel()) : this.model.stringIndex.equals(
1284 this.label, l.getLabel());
1285 }
1286 }
1287 return false;
1288 }
1289
1290 @Override
1291 public int hashCode() {
1292 return this.hash;
1293 }
1294
1295 @Override
1296 public String toString() {
1297 final String s = getCachedLabel(false);
1298 final StringBuilder builder = new StringBuilder(256);
1299 builder.append('"');
1300 if (s != null) {
1301 builder.append(s);
1302 } else {
1303 this.model.stringIndex.get(this.label, builder);
1304 }
1305 builder.append('"');
1306 if (this.langOrDatatype instanceof String) {
1307 builder.append('@').append(this.langOrDatatype);
1308 } else {
1309 builder.append("^^<").append(this.langOrDatatype).append(">");
1310 }
1311 return builder.toString();
1312 }
1313
1314 }
1315
1316 private static final class ModelStatement implements Statement {
1317
1318 private static final long serialVersionUID = 1L;
1319
1320 private static final int HASH_ZOMBIE = 0;
1321
1322 private static final int HASH_UNCACHED = 1;
1323
1324 int hash;
1325
1326 final ModelResource subj;
1327
1328 final ModelURI pred;
1329
1330 final ModelValue obj;
1331
1332 final ModelResource ctx;
1333
1334 @Nullable
1335 transient ModelStatement nextBySubj;
1336
1337 @Nullable
1338 transient ModelStatement nextByPred;
1339
1340 @Nullable
1341 transient ModelStatement nextByObj;
1342
1343 @Nullable
1344 transient ModelStatement nextByCtx;
1345
1346 ModelStatement(final ModelResource subj, final ModelURI pred, final ModelValue obj,
1347 final ModelResource ctx) {
1348
1349 final int cachedHash = 961 * subj.hashCode() + 31 * pred.hashCode() + obj.hashCode();
1350
1351 this.hash = cachedHash != HASH_ZOMBIE ? cachedHash : HASH_UNCACHED;
1352 this.subj = subj;
1353 this.pred = pred;
1354 this.obj = obj;
1355 this.ctx = ctx;
1356 }
1357
1358 @Nullable
1359 ModelStatement next(final int component) {
1360
1361 switch (component) {
1362 case SUBJ:
1363 return this.nextBySubj;
1364 case PRED:
1365 return this.nextByPred;
1366 case OBJ:
1367 return this.nextByObj;
1368 case CTX:
1369 return this.nextByCtx;
1370 default:
1371 throw new Error();
1372 }
1373 }
1374
1375 boolean match(@Nullable final ModelResource subj, @Nullable final ModelURI pred,
1376 @Nullable final ModelValue obj, @Nullable final ModelResource ctx) {
1377 return (subj == null || subj == this.subj) && (pred == null || pred == this.pred)
1378 && (obj == null || obj == this.obj) && (ctx == null || ctx == this.ctx);
1379 }
1380
1381 int hash() {
1382 return hash(this.subj, this.pred, this.obj, this.ctx);
1383 }
1384
1385 static int hash(final ModelResource subj, final ModelURI pred, final ModelValue obj,
1386 final ModelResource ctx) {
1387 return 6661 * System.identityHashCode(subj) + 961 * System.identityHashCode(pred) + 31
1388 * System.identityHashCode(obj) + System.identityHashCode(ctx);
1389 }
1390
1391 boolean isZombie() {
1392 return this.hash == HASH_ZOMBIE;
1393 }
1394
1395 void markZombie() {
1396 this.hash = HASH_ZOMBIE;
1397 }
1398
1399 @Override
1400 public Resource getSubject() {
1401 return this.subj;
1402 }
1403
1404 @Override
1405 public URI getPredicate() {
1406 return this.pred;
1407 }
1408
1409 @Override
1410 public Value getObject() {
1411 return this.obj;
1412 }
1413
1414 @Override
1415 @Nullable
1416 public Resource getContext() {
1417 if (this.ctx != null) {
1418 if (this.ctx.model != null) {
1419 if (this.ctx == this.ctx.model.valueNil) {
1420 return null;
1421 }
1422 } else {
1423 if (this.ctx.equals(SESAME.NIL)) {
1424 return null;
1425 }
1426 }
1427 }
1428 return this.ctx;
1429 }
1430
1431 @Override
1432 public boolean equals(final Object object) {
1433 if (object == this) {
1434 return true;
1435 }
1436 if (!(object instanceof Statement)) {
1437 return false;
1438 }
1439 final Statement other = (Statement) object;
1440 return this.obj.equals(other.getObject()) && this.subj.equals(other.getSubject())
1441 && this.pred.equals(other.getPredicate());
1442 }
1443
1444 @Override
1445 public int hashCode() {
1446 if (this.hash != HASH_ZOMBIE && this.hash != HASH_UNCACHED) {
1447 return this.hash;
1448 } else {
1449 return 961 * this.subj.hashCode() + 31 * this.pred.hashCode()
1450 + this.obj.hashCode();
1451 }
1452 }
1453
1454 @Override
1455 public String toString() {
1456 final StringBuilder builder = new StringBuilder(256);
1457 builder.append("(").append(this.subj).append(", ").append(this.pred).append(", ")
1458 .append(this.obj).append(")");
1459 if (!SESAME.NIL.equals(this.ctx)) {
1460 builder.append(" [").append(this.ctx).append("]");
1461 }
1462 return builder.toString();
1463 }
1464
1465 }
1466
1467 }