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.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     // NAMESPACE HANDLING
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     // STATEMENT HANDLING - CONTEXT ARRAYS
129     //
130     // the following methods performs three things:
131     // (1) they translate input values to model values, possibly creating them
132     // (2) they translate doXXX calls supplying multiple contexts to corresponding doXXX calls
133     // operating on a single context (possibly a wildcard)
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         // Null context arrays are forbidden
140         Objects.requireNonNull(ctxs);
141 
142         // Lookup SPO in the values hash table
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         // If one of SPO is missing in the table, then no statements can exist for that component
148         if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE) {
149             return 0;
150         }
151 
152         // Otherwise, handle two cases based on the contents of the context array
153         if (ctxs.length == 0) {
154             // (1) Match any context
155             return doSize(msubj, mpred, mobj, (ModelResource) null);
156 
157         } else {
158             // (2) Match multiple contexts, summing the # of statements for each of them
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         // Lookup SPO in the values hash table
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         // If one of SPOC is missing in the table, then no statements can exist for that component
180         if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE || mctx == NULL_VALUE) {
181             return 0;
182         }
183 
184         // Otherwise, delegate
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         // Null context arrays are forbidden
193         Objects.requireNonNull(ctxs);
194 
195         // Lookup SPO in the values hash table
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         // If any of SPO is missing in the table, then no statements can exist for that component
201         if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE) {
202             return Collections.emptyIterator();
203         }
204 
205         // Otherwise handle three cases based on the contexts array
206         if (ctxs.length == 0) {
207             // (1) Match any context
208             return doIterator(msubj, mpred, mobj, (ModelResource) null);
209 
210         } else if (ctxs.length == 1) {
211             // (2) Match exactly one context. If not defined, return an empty iterator
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             // (3) Match multiple contexts, concatenating the iterators for each context
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         // All SPOC components must be specified
234         Objects.requireNonNull(subj);
235         Objects.requireNonNull(pred);
236         Objects.requireNonNull(obj);
237         Objects.requireNonNull(ctxs);
238 
239         // Lookup SPO model values in the values hash table, creating them if necessary
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         // Handle two cases based on the context array
245         if (ctxs.length == 0) {
246             // (1) Add a single statement in the default context (sesame:nil)
247             return doAdd(msubj, mpred, mobj, this.valueNil);
248 
249         } else {
250             // (2) Add multiple statements in different contexts
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         // Null context arrays are forbidden
267         Objects.requireNonNull(ctxs);
268 
269         // Lookup SPO in the values hash table
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         // If any of SPO is missing in the table, then no statements can exist for that component
275         if (msubj == NULL_VALUE || mpred == NULL_VALUE || mobj == NULL_VALUE) {
276             return false;
277         }
278 
279         // Otherwise handle two cases based on the contents of the contexts array
280         if (ctxs.length == 0) {
281             // (1) Wildcard context: remove statements matching SPO in any context
282             return doRemove(msubj, mpred, mobj, (ModelResource) null);
283 
284         } else {
285             // (2) Specific contexts: remove statements matching SPO in the given contexts
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     // STATEMENT HANDLING - SINGLE CONTEXTS
305 
306     private int doSize(@Nullable final ModelResource subj, @Nullable final ModelURI pred,
307             @Nullable final ModelValue obj, @Nullable final ModelResource ctx) {
308 
309         // Select the SPOC component associated to the minimum number of statements
310         final int comp = selectComponent(subj, pred, obj, ctx);
311 
312         // If no component has been specified, return the total model size
313         if (comp < 0) {
314             return this.statementCount;
315         }
316 
317         // Otherwise, iterate over the statements associated to the component, applying the filter
318         // and returning the number of statements that matches it
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         // Select the SPOC component associated to the min number of statements
353         final int comp = selectComponent(subj, pred, obj, ctx);
354 
355         // If no component has been specified, return an iterator over all the statements
356         // The returned iterator supports element removal (delegating to removeStatement)
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         // Otherwise, build an iterator over all the statements associated to the component, and
404         // then filter it so to return only statements matching the supplied filter. The returned
405         // iterator supports statement removal (by delegating to removeStatement)
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         // Identify the first slot where the statement could be stored in the hash table
457         final int hash = ModelStatement.hash(subj, pred, obj, ctx);
458         int slot = (hash & 0x7FFFFFFF) % this.statementTable.length;
459 
460         // Scan the hash table (linear probing), doing nothing if a matching statement is found or
461         // adding the statement otherwise
462         while (true) {
463 
464             // Retrieve the statement for the current slot (if any) and handle three cases
465             ModelStatement stmt = this.statementTable[slot];
466             final boolean isNull = stmt == null;
467             if (isNull || stmt == NULL_STATEMENT) {
468 
469                 // (1) Empty/deleted slot: add the statement
470                 // First add the statement to the hash table and rehash if necessary
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                 // Then connect the statement to the various linked lists
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                 // Increment statement counters
492                 ++subj.numSubj;
493                 ++pred.numPred;
494                 ++obj.numObj;
495                 ++ctx.numCtx;
496 
497                 // Signal a statement was added
498                 return true;
499 
500             } else if (subj == stmt.subj && pred == stmt.pred && obj == stmt.obj
501                     && ctx == stmt.ctx) {
502 
503                 // (2) Statement already in the model: abort signalling nothing happened
504                 return false;
505 
506             } else {
507 
508                 // (3) Another statement in the slot: move to next slot
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         // Do nothing if model is empty
519         if (this.statementCount == 0) {
520             return false;
521         }
522 
523         // Remove exactly one statement (at most) if all the components were supplied
524         if (subj != null && pred != null && obj != null && ctx != null) {
525             return removeStatement(subj, pred, obj, ctx);
526         }
527 
528         // Select the SPOC component associated to the min number of statements
529         final int comp = selectComponent(subj, pred, obj, ctx);
530 
531         // Clear the whole model (preserving generated values) if no component was specified
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         // Remove all the statements associated to the component that satisfy the filter
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     // STATEMENT HANDLING - MISC METHODS
573 
574     private boolean removeStatement(final ModelResource subj, final ModelURI pred,
575             final ModelValue obj, final ModelResource ctx) {
576 
577         // Delete a matching statement from the hash table, aborting if it does not exist
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         // Mark the statement as deleted
595         mstmt.markZombie();
596 
597         // Update counters
598         --subj.numSubj;
599         --pred.numPred;
600         --obj.numObj;
601         --ctx.numCtx;
602         --this.statementCount;
603         ++this.statementZombies;
604 
605         // Remove zombie statements if too many
606         if (this.statementZombies >= this.statementCount) {
607             cleanZombies();
608         }
609 
610         // Signal that a statement was removed
611         return true;
612     }
613 
614     private ModelValue lookupValue(@Nullable final Value value, final boolean canCreate) {
615 
616         // Handle null case
617         if (value == null) {
618             return null;
619         }
620 
621         // Handle the case the value is already a model value belonging to this model
622         if (value instanceof ModelValue) {
623             final ModelValue mv = (ModelValue) value;
624             if (mv.model == this) {
625                 return mv;
626             }
627         }
628 
629         // Lookup the model value in the hash table
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                 // Return null if missing and cannot create
638                 if (!canCreate) {
639                     return NULL_VALUE;
640                 }
641 
642                 // Otherwise create the model value
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                 // Add the model value to the hash table and rehash if necessary
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                 // Return inserted model value
669                 return mv;
670 
671             } else if (mv.equals(value)) {
672 
673                 // Value already in the table: return it
674                 return mv;
675 
676             } else {
677 
678                 // Move to next slot
679                 slot = incrementSlot(slot, this.valueTable.length);
680 
681             }
682         }
683     }
684 
685     private void rehashValues() {
686 
687         // Abort if there is no need for rehashing
688         if (this.valueSlots < this.valueTable.length / 2) {
689             return;
690         }
691 
692         // Compute the new table size based on the number of stored values and NOT on the
693         // number of filled slots (which may contain the NULL_VALUE marker)
694         final int newLength = this.valueTable.length
695                 * (this.valueCount * 2 >= this.valueTable.length ? 2 : 1);
696 
697         // Replace the statement hash table with a possibly larger table
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         // The number of used slots is now equal to the number of stored values
712         this.valueSlots = this.valueCount;
713     }
714 
715     private void rehashStatements() {
716 
717         // Abort if there is no need for re-hashing
718         if (this.statementSlots * 2 < this.statementTable.length) {
719             return;
720         }
721 
722         // Compute the new table size based on the number of stored statements and NOT on the
723         // number of filled slots (which may contain the NULL_STATEMENT marker)
724         final int newLength = this.statementTable.length
725                 * (this.statementCount * 2 >= this.statementTable.length ? 2 : 1);
726 
727         // Replace the statement hash table with a possibly larger table
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         // The number of used slots is now equal to the number of stored statements
742         this.statementSlots = this.statementCount;
743     }
744 
745     private void cleanZombies() {
746 
747         // Iterate over the values in this model, removing zombie statements from SPOC lists
748         ModelStatement stmt;
749         ModelStatement prev;
750         for (final ModelValue mv : this.valueTable) {
751 
752             // Skip empty slots
753             if (mv == null || mv == NULL_VALUE) {
754                 continue;
755             }
756 
757             // Remove zombie statements from object list
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             // Proceed only if the value is a Resource with subject and context lists
769             if (!(mv instanceof ModelResource)) {
770                 continue;
771             }
772             final ModelResource mr = (ModelResource) mv;
773 
774             // Remove zombie statements from subject list
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             // Remove zombie statements from context list
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             // Proceed only if the value is a URI with predicate list
797             if (!(mv instanceof ModelURI)) {
798                 continue;
799             }
800             final ModelURI mu = (ModelURI) mv;
801 
802             // Remove zombie statements from predicate list
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         // Reset zombie counter
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         // Start with no component selected
823         int result = -1;
824         int num = Integer.MAX_VALUE;
825 
826         // Then, choose the component with the minimum number of associated statements
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         // Return either a component ID or a negative value if no component was specified
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 }