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.Serializable;
17  import java.util.AbstractCollection;
18  import java.util.AbstractSet;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.LinkedHashSet;
25  import java.util.NoSuchElementException;
26  import java.util.Objects;
27  import java.util.Set;
28  import java.util.function.Function;
29  
30  import javax.annotation.Nullable;
31  
32  import com.google.common.collect.Iterables;
33  import com.google.common.collect.Lists;
34  
35  import org.openrdf.model.Graph;
36  import org.openrdf.model.Literal;
37  import org.openrdf.model.Model;
38  import org.openrdf.model.Namespace;
39  import org.openrdf.model.Resource;
40  import org.openrdf.model.Statement;
41  import org.openrdf.model.URI;
42  import org.openrdf.model.Value;
43  import org.openrdf.model.ValueFactory;
44  import org.openrdf.model.impl.NamespaceImpl;
45  import org.openrdf.model.impl.URIImpl;
46  import org.openrdf.model.impl.ValueFactoryImpl;
47  import org.openrdf.model.util.ModelException;
48  import org.openrdf.query.BindingSet;
49  import org.openrdf.query.Dataset;
50  import org.openrdf.query.QueryEvaluationException;
51  import org.openrdf.query.algebra.StatementPattern;
52  import org.openrdf.query.algebra.TupleExpr;
53  import org.openrdf.query.algebra.Var;
54  import org.openrdf.query.algebra.evaluation.TripleSource;
55  import org.openrdf.query.algebra.evaluation.impl.EvaluationStatistics;
56  import org.openrdf.query.algebra.evaluation.impl.EvaluationStrategyImpl;
57  
58  import info.aduna.iteration.CloseableIteration;
59  
60  public abstract class QuadModel extends AbstractCollection<Statement> implements Graph,
61          Serializable {
62  
63      protected final static Resource[] CTX_ANY = new Resource[0];
64  
65      protected final static Resource[] CTX_DEFAULT = new Resource[] { null };
66  
67      private static final long serialVersionUID = 1L;
68  
69      public static QuadModel create() {
70          return new QuadModelImpl();
71      }
72  
73      public static QuadModel create(final Iterable<Statement> statements) {
74          final QuadModel model = create();
75          Iterables.addAll(model, statements);
76          return model;
77      }
78  
79      /**
80       * Returns a {@code QuadModel} view of the supplied {@code SailConnection}. Given to the use
81       * of internal locks in some SAIL implementations (e.g., the MemoryStore), the returned view
82       * should be used only inside a thread, similarly to the SailConnection it wraps. Parameter
83       * {@code trackChanges} enables or disables the checks performed each time a statement is
84       * added or removed that the model was changed.
85       *
86       * @param connection
87       *            the connection to wrap
88       * @param trackChanges
89       *            true, if addition/deletion operations should return true or false based on
90       *            whether the model was actually changed by the operation; if false, the check is
91       *            skipped and all modification operations return true
92       * @return the created {@code QuadModel} view
93       */
94      public static QuadModel wrap(final org.openrdf.sail.SailConnection connection,
95              final boolean trackChanges) {
96          return new QuadModelSailAdapter(connection, trackChanges);
97      }
98  
99      public static QuadModel wrap(final org.openrdf.repository.RepositoryConnection connection,
100             final boolean trackChanges) {
101         return new QuadModelRepositoryAdapter(connection, trackChanges);
102     }
103 
104     public static QuadModel wrap(final Model model) {
105         return new QuadModelModelAdapter(model);
106     }
107 
108     protected abstract Set<Namespace> doGetNamespaces();
109 
110     @Nullable
111     protected abstract Namespace doGetNamespace(String prefix);
112 
113     @Nullable
114     protected abstract Namespace doSetNamespace(String prefix, String name);
115 
116     protected abstract int doSize(@Nullable Resource subj, @Nullable URI pred,
117             @Nullable Value obj, Resource[] ctxs);
118 
119     protected int doSizeEstimate(@Nullable final Resource subj, @Nullable final URI pred,
120             @Nullable final Value obj, @Nullable final Resource ctx) {
121         return -1;
122     }
123 
124     protected abstract Iterator<Statement> doIterator(@Nullable final Resource subj,
125             @Nullable final URI pred, @Nullable final Value obj, final Resource[] ctxs);
126 
127     protected abstract boolean doAdd(@Nullable Resource subj, @Nullable URI pred,
128             @Nullable Value obj, Resource[] ctxs);
129 
130     protected abstract boolean doRemove(@Nullable Resource subj, @Nullable URI pred,
131             @Nullable Value obj, Resource[] ctxs);
132 
133     protected Iterator<BindingSet> doEvaluate(final TupleExpr expr,
134             @Nullable final Dataset dataset, @Nullable final BindingSet bindings) {
135 
136         return Algebra.evaluateTupleExpr(expr, dataset, bindings, new EvaluationStrategyImpl(
137                 getTripleSource(), dataset, Algebra.getFederatedServiceResolver()),
138                 getEvaluationStatistics(), getValueNormalizer());
139     }
140 
141     protected Value doNormalize(@Nullable final Value value) {
142         return value;
143     }
144 
145     public final QuadModel unmodifiable() {
146         return this instanceof UnmodifiableModel ? this : new UnmodifiableModel(this);
147     }
148 
149     /**
150      * Gets the namespaces associated to this quad model.
151      *
152      * @return the namespaces
153      */
154     public final Set<Namespace> getNamespaces() {
155         return doGetNamespaces();
156     }
157 
158     /**
159      * Gets the namespace for the specified prefix, if any.
160      *
161      * @param prefix
162      *            the namespace prefix
163      * @return the namespace for the specified prefix, if defined, otherwise null
164      */
165     @Nullable
166     public final Namespace getNamespace(final String prefix) {
167         return doGetNamespace(prefix);
168     }
169 
170     /**
171      * Sets the prefix for a namespace.
172      *
173      * @param prefix
174      *            the prefix
175      * @param name
176      *            the namespace that the prefix maps to
177      * @return the {@link Namespace} object for the given namespace
178      */
179     public final Namespace setNamespace(final String prefix, final String name) {
180         doSetNamespace(Objects.requireNonNull(prefix), Objects.requireNonNull(name));
181         return new NamespaceImpl(prefix, name);
182     }
183 
184     /**
185      * Sets the prefix for a namespace.
186      *
187      * @param namespace
188      *            a {@link Namespace} object to use in this Model.
189      */
190     public final void setNamespace(final Namespace namespace) {
191         doSetNamespace(namespace.getPrefix(), namespace.getName());
192     }
193 
194     /**
195      * Removes a namespace declaration.
196      *
197      * @param prefix
198      *            the prefix
199      * @return the previous namespace bound to the prefix, if any, otherwise null
200      */
201     @Nullable
202     public final Namespace removeNamespace(final String prefix) {
203         return doSetNamespace(Objects.requireNonNull(prefix), null);
204     }
205 
206     @Override
207     public final boolean isEmpty() {
208         final Iterator<Statement> iterator = doIterator(null, null, null, CTX_ANY);
209         try {
210             return !iterator.hasNext();
211         } finally {
212             IO.closeQuietly(iterator);
213         }
214     }
215 
216     @Override
217     public final int size() {
218         return doSize(null, null, null, CTX_ANY);
219     }
220 
221     public final int size(@Nullable final Resource subj, @Nullable final URI pred,
222             @Nullable final Value obj) {
223         return doSize(subj, pred, obj, CTX_ANY);
224     }
225 
226     public final int size(@Nullable final Resource subj, @Nullable final URI pred,
227             @Nullable final Value obj, final Resource... contexts) {
228         return doSize(subj, pred, obj, contexts);
229     }
230 
231     public final int sizeEstimate(@Nullable final Resource subj, @Nullable final URI pred,
232             @Nullable final Value obj, final Resource... contexts) {
233         if (contexts.length == 0) {
234             return doSizeEstimate(subj, pred, obj, null);
235         } else {
236             int estimate = 0;
237             for (final Resource ctx : contexts) {
238                 final int delta = doSizeEstimate(subj, pred, obj, ctx);
239                 if (ctx == null) {
240                     return delta;
241                 }
242                 estimate += delta;
243             }
244             return estimate;
245         }
246     }
247 
248     @Override
249     public final Iterator<Statement> iterator() {
250         return doIterator(null, null, null, CTX_ANY);
251     }
252 
253     public final Iterator<Statement> iterator(@Nullable final Resource subj,
254             @Nullable final URI pred, @Nullable final Value obj) {
255         return doIterator(subj, pred, obj, CTX_ANY);
256     }
257 
258     public final Iterator<Statement> iterator(@Nullable final Resource subj,
259             @Nullable final URI pred, @Nullable final Value obj, final Resource... contexts) {
260         return doIterator(subj, pred, obj, contexts);
261     }
262 
263     public final Iterator<BindingSet> evaluate(final TupleExpr expr,
264             @Nullable final Dataset dataset, @Nullable final BindingSet bindings) {
265         return doEvaluate(Objects.requireNonNull(expr), dataset, bindings);
266     }
267 
268     @Override
269     public final boolean contains(@Nullable final Object object) {
270         if (object instanceof Statement) {
271             final Statement stmt = (Statement) object;
272             return contains(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(),
273                     new Resource[] { stmt.getContext() });
274         }
275         return false;
276     }
277 
278     public final boolean contains(@Nullable final Resource subj, @Nullable final URI pred,
279             @Nullable final Value obj) {
280         return contains(subj, pred, obj, CTX_ANY);
281     }
282 
283     /**
284      * Determines if statements with the specified subject, predicate, object and (optionally)
285      * context exist in this model. The {@code subject}, {@code predicate} and {@code object}
286      * parameters can be null to indicate wildcards. The {@code contexts} parameter is a wildcard
287      * and accepts zero or more values. If no contexts are specified, statements will match
288      * disregarding their context. If one or more contexts are specified, statements with a
289      * context matching one of these will match. Note: to match statements without an associated
290      * context, specify the value null and explicitly cast it to type {@code Resource}.
291      *
292      * @param subj
293      *            the subject to match, or null to match any subject
294      * @param pred
295      *            the predicate to match, or null to match any predicate
296      * @param obj
297      *            the object to match, or null to match any object
298      * @param ctxs
299      *            the contexts to match; if empty, any statement context will be matched
300      * @return true, if there are statements matching the specified pattern
301      */
302     public final boolean contains(@Nullable final Resource subj, @Nullable final URI pred,
303             @Nullable final Value obj, final Resource... ctxs) {
304         final Iterator<Statement> iterator = doIterator(subj, pred, obj, ctxs);
305         try {
306             return iterator.hasNext();
307         } finally {
308             IO.closeQuietly(iterator);
309         }
310     }
311 
312     @Override
313     public final boolean add(final Statement stmt) {
314         return doAdd(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(),
315                 new Resource[] { stmt.getContext() });
316     }
317 
318     public final boolean add(@Nullable final Resource subj, @Nullable final URI pred,
319             @Nullable final Value obj) {
320         return doAdd(subj, pred, obj, CTX_ANY);
321     }
322 
323     /**
324      * Adds one or more statements to the quad model. This method adds a statement for each
325      * specified context. If no context is specified, a single statement with no associated
326      * context is added. If this Model is a filtered Model then null (if context empty) values are
327      * permitted and will use the corresponding filtered values.
328      *
329      * @param subj
330      *            the subject
331      * @param pred
332      *            the predicate
333      * @param obj
334      *            the object.
335      * @param ctxs
336      *            the contexts to add statements to.
337      * @return true if the model changed as a result of the operation
338      * @throws IllegalArgumentException
339      *             if this quad model cannot store the given statement, because it is filtered out
340      *             of this view.
341      * @throws UnsupportedOperationException
342      *             if this quad model cannot accept any statements, because it is filtered to the
343      *             empty set.
344      */
345     @Override
346     public final boolean add(@Nullable final Resource subj, @Nullable final URI pred,
347             @Nullable final Value obj, final Resource... ctxs) {
348         return doAdd(subj, pred, obj, ctxs);
349     }
350 
351     @Override
352     public final void clear() {
353         doRemove(null, null, null, CTX_ANY);
354     }
355 
356     /**
357      * Removes statements with the specified context existing in this quad model.
358      *
359      * @param contexts
360      *            the contexts of the statements to remove
361      * @return true, if one or more statements have been removed.
362      */
363     public final boolean clear(final Resource... contexts) {
364         return doRemove(null, null, null, contexts);
365     }
366 
367     @Override
368     public final boolean remove(@Nullable final Object object) {
369         if (object instanceof Statement) {
370             final Statement stmt = (Statement) object;
371             return doRemove(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(),
372                     new Resource[] { stmt.getContext() });
373         }
374         return false;
375     }
376 
377     public final boolean remove(@Nullable final Resource subj, @Nullable final URI pred,
378             @Nullable final Value obj) {
379         return doRemove(subj, pred, obj, CTX_ANY);
380     }
381 
382     /**
383      * Removes statements with the specified subject, predicate, object and (optionally) context
384      * existing in this model. The {@code subject}, {@code predicate} and {@code object}
385      * parameters can be null to indicate wildcards. The {@code contexts} parameter is a wildcard
386      * and accepts zero or more values. If no context is specified, statements will be removed
387      * disregarding their context. If one or more contexts are specified, statements with a
388      * context matching one of these will be removed. Note: to remove statements without an
389      * associated context, specify the value null and explicitly cast it to type {@code Resource}.
390      *
391      * @param subj
392      *            the subject to match, or null to match any subject
393      * @param pred
394      *            the predicate to match, or null to match any predicate
395      * @param obj
396      *            the object to match, or null to match any object
397      * @param ctxs
398      *            the contexts to match; if empty, any statement context will be matched
399      * @return true, if one or more statements have been removed.
400      */
401     public final boolean remove(@Nullable final Resource subj, @Nullable final URI pred,
402             @Nullable final Value obj, final Resource... ctxs) {
403         return doRemove(subj, pred, obj, ctxs);
404     }
405 
406     @Override
407     @SuppressWarnings({ "unchecked", "rawtypes" })
408     public boolean removeAll(final Collection<?> c) {
409         Collection<?> toRemove = c;
410         if ((c instanceof Set<?> || c instanceof Model || c instanceof QuadModel)
411                 && c.size() > size()) {
412             toRemove = Lists.newArrayList();
413             for (final Statement stmt : this) {
414                 if (c.contains(stmt)) {
415                     ((Collection) toRemove).add(stmt);
416                 }
417             }
418         }
419         boolean modified = false;
420         for (final Object element : toRemove) {
421             final boolean removed = remove(element);
422             modified |= removed;
423         }
424         return modified;
425     }
426 
427     /**
428      * Returns a view of the statements with the specified subject, predicate, object and
429      * (optionally) context. The {@code subject}, {@code predicate} and {@code object} parameters
430      * can be null to indicate wildcards. The {@code contexts} parameter is a wildcard and accepts
431      * zero or more values. If no context is specified, statements will match disregarding their
432      * context. If one or more contexts are specified, statements with a context matching one of
433      * these will match. Note: to match statements without an associated context, specify the
434      * value null and explicitly cast it to type {@code Resource}. The returned quad model is
435      * backed by this model, so changes to this model are reflected in the returned model, and
436      * vice-versa. If this quad model is modified while an iteration over the returned model is in
437      * progress (except through the iterator's own {@code remove} operation), the results of the
438      * iteration are undefined. The returned quad model supports element removal, which removes
439      * the corresponding statement from this model. Statements can be added to the returned quad
440      * model only if they match the filter pattern.
441      *
442      * @param subj
443      *            the subject to match, or null to match any subject
444      * @param pred
445      *            the predicate to match, or null to match any predicate
446      * @param obj
447      *            the object to match, or null to match any object
448      * @param ctxs
449      *            the contexts to match; if empty, any statement context will be matched
450      * @return a quad model view with the statements matching the pattern specified
451      */
452     public QuadModel filter(@Nullable final Resource subj, @Nullable final URI pred,
453             @Nullable final Value obj, final Resource... ctxs) {
454         return new FilteredModel(this, subj, pred, obj, ctxs);
455     }
456 
457     /**
458      * Returns an <i>immutable</i> view of a subset of this model, containing the specified
459      * statements. The supplied collection of statements <b>must</b> not contain duplicates and
460      * must be a subset of the statements in this model.
461      *
462      * @param statements
463      *            the statements of this model to include in the returned immutable view, without
464      *            duplicates
465      * @return an immutable view of this model including only the statements specified
466      */
467     public QuadModel filter(final Collection<Statement> statements) {
468         return new QuadModelSubModel(this, statements);
469     }
470 
471     /**
472      * Returns a {@link Set} view of the subjects contained in this model. The set is backed by
473      * this model, so changes to this model are reflected in the set, and vice-versa. If the model
474      * is modified while an iteration over the set is in progress (except through the iterator's
475      * own {@code remove} operation), the results of the iteration are undefined. The set supports
476      * element removal, which removes the corresponding statement from the model. It does not
477      * support element addition, unless this quad model is a filtered quad model with non-null
478      * {@code pred} and {@code obj} filter parameteres.
479      *
480      * @return a set view of the subjects contained in this quad model
481      */
482     public final Set<Resource> subjects() {
483         return new ValueSet<Resource>() {
484 
485             @Override
486             public boolean contains(final Object object) {
487                 if (object instanceof Resource) {
488                     return QuadModel.this.contains((Resource) object, null, null);
489                 }
490                 return false;
491             }
492 
493             @Override
494             public boolean remove(final Object object) {
495                 if (object instanceof Resource) {
496                     return doRemove((Resource) object, null, null, null);
497                 }
498                 return false;
499             }
500 
501             @Override
502             public boolean add(final Resource subj) {
503                 return doAdd(subj, null, null, null);
504             }
505 
506             @Override
507             Resource term(final Statement stmt) {
508                 return stmt.getSubject();
509             }
510 
511         };
512     }
513 
514     /**
515      * Returns a {@link Set} view of the predicates contained in this model. The set is backed by
516      * this model, so changes to this model are reflected in the set, and vice-versa. If the model
517      * is modified while an iteration over the set is in progress (except through the iterator's
518      * own {@code remove} operation), the results of the iteration are undefined. The set supports
519      * element removal, which removes the corresponding statement from the model. It does not
520      * support element addition, unless this is a filtered quad model with non-null {@code subj}
521      * and {@code obj} filter parameters.
522      *
523      * @return a set view of the predicates contained in this quad model
524      */
525     public final Set<URI> predicates() {
526         return new ValueSet<URI>() {
527 
528             @Override
529             public boolean contains(final Object object) {
530                 if (object instanceof URI) {
531                     return QuadModel.this.contains(null, (URI) object, null);
532                 }
533                 return false;
534             }
535 
536             @Override
537             public boolean remove(final Object object) {
538                 if (object instanceof URI) {
539                     return doRemove(null, (URI) object, null, null);
540                 }
541                 return false;
542             }
543 
544             @Override
545             public boolean add(final URI pred) {
546                 return doAdd(null, pred, null, null);
547             }
548 
549             @Override
550             URI term(final Statement stmt) {
551                 return stmt.getPredicate();
552             }
553 
554         };
555     }
556 
557     /**
558      * Returns a {@link Set} view of the objects contained in this model. The set is backed by
559      * this model, so changes to this model are reflected in the set, and vice-versa. If the model
560      * is modified while an iteration over the set is in progress (except through the iterator's
561      * own {@code remove} operation), the results of the iteration are undefined. The set supports
562      * element removal, which removes the corresponding statement from the model. It does not
563      * support element addition, unless this is a filtered quad model with non-null {@code subj}
564      * and {@code pred} filter parameters.
565      *
566      * @return a set view of the objects contained in this quad model
567      */
568     public final Set<Value> objects() {
569         return new ValueSet<Value>() {
570 
571             @Override
572             public boolean contains(final Object object) {
573                 if (object instanceof Value) {
574                     return QuadModel.this.contains(null, null, (Value) object);
575                 }
576                 return false;
577             }
578 
579             @Override
580             public boolean remove(final Object object) {
581                 if (object instanceof Value) {
582                     return doRemove(null, null, (Value) object, null);
583                 }
584                 return false;
585             }
586 
587             @Override
588             public boolean add(final Value obj) {
589                 return doAdd(null, null, obj, null);
590             }
591 
592             @Override
593             Value term(final Statement stmt) {
594                 return stmt.getObject();
595             }
596 
597         };
598     }
599 
600     /**
601      * Returns a {@link Set} view of the contexts contained in this model. The set is backed by
602      * this model, so changes to this model are reflected in the set, and vice-versa. If the model
603      * is modified while an iteration over the set is in progress (except through the iterator's
604      * own {@code remove} operation), the results of the iteration are undefined. The set supports
605      * element removal, which removes the corresponding statement from the model. It does not
606      * support element addition, unless this is a filtered quad model with non-null {@code subj},
607      * {@code pred} and {@code obj} filter parameters.
608      *
609      * @return a set view of the contexts contained in this quad model
610      */
611     public final Set<Resource> contexts() {
612         return new ValueSet<Resource>() {
613 
614             @Override
615             public boolean contains(final Object object) {
616                 if (object instanceof Resource || object == null) {
617                     return QuadModel.this.contains(null, null, null, (Resource) object);
618                 }
619                 return false;
620             }
621 
622             @Override
623             public boolean remove(final Object object) {
624                 if (object instanceof Resource || object == null) {
625                     return doRemove(null, null, null, new Resource[] { (Resource) object });
626                 }
627                 return false;
628             }
629 
630             @Override
631             public boolean add(final Resource context) {
632                 return doAdd(null, null, null, new Resource[] { context });
633             }
634 
635             @Override
636             Resource term(final Statement stmt) {
637                 return stmt.getContext();
638             }
639 
640         };
641     }
642 
643     /**
644      * Gets the object of the statement(s). If the quad model contains one or more statements, all
645      * these statements should have the same object. A {@link ModelException} is thrown if this is
646      * not the case.
647      *
648      * @return the object of the statement(s) in this model, or null if this model is empty
649      * @throws ModelException
650      *             if multiple objects are present
651      */
652     @Nullable
653     public final Value objectValue() throws ModelException {
654         final Iterator<Value> iter = objects().iterator();
655         if (iter.hasNext()) {
656             final Value obj = iter.next();
657             if (iter.hasNext()) {
658                 throw new ModelException(obj, iter.next());
659             }
660             return obj;
661         }
662         return null;
663     }
664 
665     /**
666      * Utility method that casts the return value of {@link #objectValue()} to a Literal, or
667      * throws a ModelException if that value is not a Literal.
668      *
669      * @return the literal object of statement(s) in this model, or null if this model is empty
670      * @throws ModelException
671      *             if multiple objects are present, or if the unique object is not a Literal
672      */
673     @Nullable
674     public final Literal objectLiteral() throws ModelException {
675         final Value obj = objectValue();
676         if (obj == null) {
677             return null;
678         }
679         if (obj instanceof Literal) {
680             return (Literal) obj;
681         }
682         throw new ModelException(obj);
683     }
684 
685     /**
686      * Utility method that casts the return value of {@link #objectValue()} to a Resource, or
687      * throws a ModelException if that value is not a Resource.
688      *
689      * @return the resource object of statement(s) in this model, or null if this model is empty
690      * @throws ModelException
691      *             if multiple objects are present, or if the unique object is not a resource
692      */
693     @Nullable
694     public final Resource objectResource() throws ModelException {
695         final Value obj = objectValue();
696         if (obj == null) {
697             return null;
698         }
699         if (obj instanceof Resource) {
700             return (Resource) obj;
701         }
702         throw new ModelException(obj);
703     }
704 
705     /**
706      * Utility method that casts the return value of {@link #objectValue()} to a URI, or throws a
707      * ModelException if that value is not a URI.
708      *
709      * @return the URI object of statement(s) in this model, or null if this model is empty
710      * @throws ModelException
711      *             if multiple objects are present, or if the unique object is not a URI
712      */
713     @Nullable
714     public final URI objectURI() throws ModelException {
715         final Value obj = objectValue();
716         if (obj == null) {
717             return null;
718         }
719         if (obj instanceof URI) {
720             return (URI) obj;
721         }
722         throw new ModelException(obj);
723     }
724 
725     /**
726      * Utility method that returns the string value of {@link #objectValue()}.
727      *
728      * @return the string value of the unique object of statement(s) in this model, or null if
729      *         this model is empty
730      * @throws ModelException
731      *             if multiple objects are present
732      */
733     @Nullable
734     public final String objectString() throws ModelException {
735         final Value obj = objectValue();
736         if (obj == null) {
737             return null;
738         }
739         return obj.stringValue();
740     }
741 
742     @Deprecated
743     @Override
744     public final Iterator<Statement> match(@Nullable final Resource subj,
745             @Nullable final URI pred, @Nullable final Value obj, final Resource... contexts) {
746         return filter(subj, pred, obj, contexts).iterator();
747     }
748 
749     @Deprecated
750     @Override
751     public final ValueFactory getValueFactory() {
752         return ValueFactoryImpl.getInstance();
753     }
754 
755     public final TripleSource getTripleSource() {
756         return new TripleSource() {
757 
758             @Override
759             public ValueFactory getValueFactory() {
760                 return Statements.VALUE_FACTORY;
761             }
762 
763             @Override
764             public CloseableIteration<? extends Statement, QueryEvaluationException> getStatements(
765                     final Resource subj, final URI pred, final Value obj,
766                     final Resource... contexts) throws QueryEvaluationException {
767                 return Iterators.toIteration(QuadModel.this.doIterator(subj, pred, obj, contexts));
768             }
769 
770         };
771     }
772 
773     public final EvaluationStatistics getEvaluationStatistics() {
774 
775         return Algebra.getEvaluationStatistics((final StatementPattern pattern) -> {
776 
777             final Var sv = pattern.getSubjectVar();
778             final Var pv = pattern.getPredicateVar();
779             final Var ov = pattern.getObjectVar();
780             final Var cv = pattern.getContextVar();
781 
782             final Resource s = sv == null || !(sv.getValue() instanceof Resource) ? null
783                     : (Resource) sv.getValue();
784             final URI p = pv == null || !(pv.getValue() instanceof URI) ? null : (URI) pv
785                     .getValue();
786             final Value o = ov == null ? null : ov.getValue();
787             final Resource c = cv == null || !(cv.getValue() instanceof Resource) ? null
788                     : (Resource) cv.getValue();
789 
790             return doSizeEstimate(s, p, o, c);
791 
792         });
793     }
794 
795     public final Function<Value, Value> getValueNormalizer() {
796         return new Function<Value, Value>() {
797 
798             @Override
799             public Value apply(final Value value) {
800                 return doNormalize(value);
801             }
802 
803         };
804     }
805 
806     @SuppressWarnings("unchecked")
807     public <T extends Value> T normalize(final T value) {
808         return (T) doNormalize(value);
809     }
810 
811     private abstract class ValueSet<V extends Value> extends AbstractSet<V> {
812 
813         @Override
814         public boolean isEmpty() {
815             return QuadModel.this.isEmpty();
816         }
817 
818         @Override
819         public int size() {
820             final Iterator<Statement> iter = QuadModel.this.iterator();
821             final Set<V> set = new HashSet<>();
822             while (iter.hasNext()) {
823                 set.add(term(iter.next()));
824             }
825             return set.size();
826         }
827 
828         @Override
829         public Iterator<V> iterator() {
830             return new ValueSetIterator(QuadModel.this.iterator());
831         }
832 
833         @Override
834         public void clear() {
835             QuadModel.this.clear();
836         }
837 
838         @Override
839         public abstract boolean add(V term);
840 
841         abstract V term(Statement st);
842 
843         private final class ValueSetIterator implements Iterator<V> {
844 
845             private final Iterator<Statement> iter;
846 
847             private final Set<V> set = new LinkedHashSet<V>();
848 
849             private Statement current;
850 
851             private Statement next;
852 
853             private ValueSetIterator(final Iterator<Statement> iter) {
854                 this.iter = iter;
855             }
856 
857             @Override
858             public boolean hasNext() {
859                 if (this.next == null) {
860                     this.next = findNext();
861                 }
862                 return this.next != null;
863             }
864 
865             @Override
866             public V next() {
867                 if (this.next == null) {
868                     this.next = findNext();
869                     if (this.next == null) {
870                         throw new NoSuchElementException();
871                     }
872                 }
873                 this.current = this.next;
874                 this.next = null;
875                 final V value = term(this.current);
876                 this.set.add(value);
877                 return value;
878             }
879 
880             @Override
881             public void remove() {
882                 if (this.current == null) {
883                     throw new IllegalStateException();
884                 }
885                 ValueSet.this.remove(this.current);
886                 this.current = null;
887             }
888 
889             private Statement findNext() {
890                 while (this.iter.hasNext()) {
891                     final Statement st = this.iter.next();
892                     if (accept(st)) {
893                         return st;
894                     }
895                 }
896                 return null;
897             }
898 
899             private boolean accept(final Statement st) {
900                 return !this.set.contains(term(st));
901             }
902         }
903 
904     }
905 
906     private static final class UnmodifiableModel extends QuadModel {
907 
908         private static final long serialVersionUID = 1L;
909 
910         private final QuadModel model;
911 
912         UnmodifiableModel(final QuadModel model) {
913             this.model = model;
914         }
915 
916         @Override
917         protected Set<Namespace> doGetNamespaces() {
918             return this.model.doGetNamespaces();
919         }
920 
921         @Override
922         protected Namespace doGetNamespace(final String prefix) {
923             return this.model.doGetNamespace(prefix);
924         }
925 
926         @Override
927         protected Namespace doSetNamespace(final String prefix, final String name) {
928             throw new UnsupportedOperationException();
929         }
930 
931         @Override
932         protected int doSize(@Nullable final Resource subj, @Nullable final URI pred,
933                 @Nullable final Value obj, @Nullable final Resource[] ctxs) {
934             return this.model.doSize(subj, pred, obj, ctxs);
935         }
936 
937         @Override
938         protected int doSizeEstimate(@Nullable final Resource subj, @Nullable final URI pred,
939                 @Nullable final Value obj, @Nullable final Resource ctx) {
940             return this.model.doSizeEstimate(subj, pred, obj, ctx);
941         }
942 
943         @Override
944         protected Iterator<Statement> doIterator(@Nullable final Resource subj,
945                 @Nullable final URI pred, @Nullable final Value obj, final Resource[] ctxs) {
946             return Iterators.unmodifiable(doIterator(subj, pred, obj, ctxs));
947         }
948 
949         @Override
950         protected boolean doAdd(@Nullable final Resource subj, @Nullable final URI pred,
951                 @Nullable final Value obj, final Resource[] ctxs) {
952             throw new UnsupportedOperationException();
953         }
954 
955         @Override
956         protected boolean doRemove(@Nullable final Resource subj, @Nullable final URI pred,
957                 @Nullable final Value obj, final Resource[] ctxs) {
958             throw new UnsupportedOperationException();
959         }
960 
961         @Override
962         protected Iterator<BindingSet> doEvaluate(final TupleExpr expr,
963                 @Nullable final Dataset dataset, @Nullable final BindingSet bindings) {
964             return this.model.doEvaluate(expr, dataset, bindings);
965         }
966 
967     }
968 
969     private static final class EmptyModel extends QuadModel {
970 
971         private static final long serialVersionUID = 1L;
972 
973         private final QuadModel model;
974 
975         EmptyModel(final QuadModel model) {
976             this.model = model;
977         }
978 
979         @Override
980         protected Set<Namespace> doGetNamespaces() {
981             return this.model.doGetNamespaces();
982         }
983 
984         @Override
985         protected Namespace doGetNamespace(final String prefix) {
986             return this.model.doGetNamespace(prefix);
987         }
988 
989         @Override
990         protected Namespace doSetNamespace(final String prefix, final String name) {
991             return this.model.doSetNamespace(prefix, name);
992         }
993 
994         @Override
995         protected int doSize(@Nullable final Resource subj, @Nullable final URI pred,
996                 @Nullable final Value obj, final Resource[] ctxs) {
997             return 0;
998         }
999 
1000         @Override
1001         protected int doSizeEstimate(@Nullable final Resource subj, @Nullable final URI pred,
1002                 @Nullable final Value obj, @Nullable final Resource ctx) {
1003             return 0;
1004         }
1005 
1006         @Override
1007         protected Iterator<Statement> doIterator(@Nullable final Resource subj,
1008                 @Nullable final URI pred, @Nullable final Value obj, final Resource[] ctxs) {
1009             return Collections.emptyIterator();
1010         }
1011 
1012         @Override
1013         protected boolean doAdd(@Nullable final Resource subj, @Nullable final URI pred,
1014                 @Nullable final Value obj, final Resource[] ctxs) {
1015             throw new UnsupportedOperationException("All statements are filtered out of view");
1016         }
1017 
1018         @Override
1019         protected boolean doRemove(@Nullable final Resource subj, @Nullable final URI pred,
1020                 @Nullable final Value obj, final Resource[] ctxs) {
1021             return false;
1022         }
1023 
1024         @Override
1025         protected Iterator<BindingSet> doEvaluate(final TupleExpr expr,
1026                 @Nullable final Dataset dataset, @Nullable final BindingSet bindings) {
1027             return Collections.emptyIterator();
1028         }
1029 
1030         @Override
1031         public QuadModel filter(@Nullable final Resource subj, @Nullable final URI pred,
1032                 @Nullable final Value obj, final Resource... ctxs) {
1033             return this;
1034         }
1035 
1036     }
1037 
1038     private final static class FilteredModel extends QuadModel {
1039 
1040         private static final URI EMPTY = new URIImpl("sesame:empty");
1041 
1042         private static final long serialVersionUID = 1L;
1043 
1044         private final QuadModel model;
1045 
1046         @Nullable
1047         private final Resource subj;
1048 
1049         @Nullable
1050         private final URI pred;
1051 
1052         @Nullable
1053         private final Value obj;
1054 
1055         private final Resource[] contexts;
1056 
1057         FilteredModel(final QuadModel model, @Nullable final Resource subj,
1058                 @Nullable final URI pred, @Nullable final Value obj, final Resource[] contexts) {
1059             this.model = model;
1060             this.subj = subj;
1061             this.pred = pred;
1062             this.obj = obj;
1063             this.contexts = contexts;
1064         }
1065 
1066         @Override
1067         protected Set<Namespace> doGetNamespaces() {
1068             return this.model.doGetNamespaces();
1069         }
1070 
1071         @Override
1072         protected Namespace doGetNamespace(final String prefix) {
1073             return this.model.doGetNamespace(prefix);
1074         }
1075 
1076         @Override
1077         protected Namespace doSetNamespace(final String prefix, final String name) {
1078             return this.model.doSetNamespace(prefix, name);
1079         }
1080 
1081         @Override
1082         protected int doSize(@Nullable Resource subj, @Nullable URI pred, @Nullable Value obj,
1083                 @Nullable Resource[] ctxs) {
1084 
1085             subj = (Resource) filter(subj, this.subj);
1086             pred = (URI) filter(pred, this.pred);
1087             obj = filter(obj, this.obj);
1088             ctxs = filter(ctxs, this.contexts);
1089 
1090             return subj == EMPTY || pred == EMPTY || obj == EMPTY || ctxs == null ? 0 //
1091                     : this.model.doSize(subj, pred, obj, ctxs);
1092         }
1093 
1094         @Override
1095         protected int doSizeEstimate(@Nullable Resource subj, @Nullable URI pred,
1096                 @Nullable Value obj, @Nullable final Resource ctx) {
1097 
1098             subj = (Resource) filter(subj, this.subj);
1099             pred = (URI) filter(pred, this.pred);
1100             obj = filter(obj, this.obj);
1101             final Resource[] ctxs = filter(ctx == null ? CTX_ANY : new Resource[] { ctx },
1102                     this.contexts);
1103 
1104             if (subj == EMPTY || pred == EMPTY || obj == EMPTY || ctxs == null) {
1105                 return 0;
1106             } else if (ctxs.length == 0) {
1107                 return this.model.doSizeEstimate(subj, pred, obj, null);
1108             } else if (ctxs.length == 1) {
1109                 return this.model.doSizeEstimate(subj, pred, obj, ctxs[0]);
1110             } else {
1111                 int size = 0;
1112                 for (final Resource c : ctxs) {
1113                     size += this.model.doSizeEstimate(subj, pred, obj, c);
1114                 }
1115                 return size;
1116             }
1117         }
1118 
1119         @Override
1120         protected Iterator<Statement> doIterator(@Nullable Resource subj, @Nullable URI pred,
1121                 @Nullable Value obj, Resource[] ctxs) {
1122 
1123             subj = (Resource) filter(subj, this.subj);
1124             pred = (URI) filter(pred, this.pred);
1125             obj = filter(obj, this.obj);
1126             ctxs = filter(ctxs, this.contexts);
1127 
1128             return subj == EMPTY || pred == EMPTY || obj == EMPTY || ctxs == null ? Collections
1129                     .emptyIterator() : this.model.doIterator(subj, pred, obj, ctxs);
1130         }
1131 
1132         @Override
1133         protected boolean doAdd(@Nullable Resource subj, @Nullable URI pred, @Nullable Value obj,
1134                 Resource[] ctxs) {
1135 
1136             subj = (Resource) filter(subj, this.subj);
1137             pred = (URI) filter(pred, this.pred);
1138             obj = filter(obj, this.obj);
1139             ctxs = filter(ctxs, this.contexts);
1140 
1141             if (subj == EMPTY || pred == EMPTY || obj == EMPTY || ctxs == null) {
1142                 throw new IllegalArgumentException("Statement is filtered out of view");
1143             }
1144 
1145             return this.model.doAdd(subj, pred, obj, ctxs);
1146         }
1147 
1148         @Override
1149         protected boolean doRemove(@Nullable Resource subj, @Nullable URI pred,
1150                 @Nullable Value obj, Resource[] ctxs) {
1151 
1152             subj = (Resource) filter(subj, this.subj);
1153             pred = (URI) filter(pred, this.pred);
1154             obj = filter(obj, this.obj);
1155             ctxs = filter(ctxs, this.contexts);
1156 
1157             return subj == EMPTY || pred == EMPTY || obj == EMPTY || ctxs == null ? false //
1158                     : this.model.doRemove(subj, pred, obj, ctxs);
1159         }
1160 
1161         @Override
1162         public QuadModel filter(@Nullable Resource subj, @Nullable URI pred, @Nullable Value obj,
1163                 Resource... ctxs) {
1164 
1165             subj = (Resource) filter(subj, this.subj);
1166             pred = (URI) filter(pred, this.pred);
1167             obj = filter(obj, this.obj);
1168             ctxs = filter(ctxs, this.contexts);
1169 
1170             return subj == EMPTY || pred == EMPTY || obj == EMPTY || ctxs == null ? new EmptyModel(
1171                     this.model) : this.model.filter(subj, pred, obj, ctxs);
1172         }
1173 
1174         private static Value filter(@Nullable final Value inputValue,
1175                 @Nullable final Value thisValue) {
1176             if (inputValue == null) {
1177                 return thisValue;
1178             } else if (thisValue == null || inputValue.equals(thisValue)) {
1179                 return inputValue;
1180             } else {
1181                 return EMPTY;
1182             }
1183         }
1184 
1185         @Nullable
1186         private static Resource[] filter(final Resource[] inputCtxs, final Resource[] thisCtxs) {
1187             if (inputCtxs.length == 0) {
1188                 return thisCtxs;
1189             } else if (thisCtxs.length == 0) {
1190                 return inputCtxs;
1191             } else {
1192                 int inputIdx = 0;
1193                 outer: for (; inputIdx < inputCtxs.length; ++inputIdx) {
1194                     for (final Resource thisCtx : thisCtxs) {
1195                         if (Objects.equals(inputCtxs[inputIdx], thisCtx)) {
1196                             continue outer;
1197                         }
1198                     }
1199                     if (inputCtxs.length == 1) {
1200                         return null;
1201                     }
1202                     final Resource[] ctxs = new Resource[inputCtxs.length - 1];
1203                     if (inputIdx > 0) {
1204                         System.arraycopy(inputCtxs, 0, ctxs, 0, inputIdx);
1205                     }
1206                     int index = inputIdx;
1207                     for (++inputIdx; inputIdx < inputCtxs.length; ++inputIdx) {
1208                         for (final Resource thisCtx : thisCtxs) {
1209                             if (Objects.equals(inputCtxs[inputIdx], thisCtx)) {
1210                                 ctxs[index++] = inputCtxs[inputIdx];
1211                                 break;
1212                             }
1213                         }
1214                     }
1215                     return index == 0 ? null : index == ctxs.length ? ctxs //
1216                             : Arrays.copyOfRange(ctxs, 0, index);
1217                 }
1218                 return inputCtxs;
1219             }
1220         }
1221 
1222     }
1223 
1224 }