/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.rdf.model.impl;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.function.Supplier;

import org.apache.jena.datatypes.DatatypeFormatException ;
import org.apache.jena.datatypes.RDFDatatype ;
import org.apache.jena.datatypes.TypeMapper ;
import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.datatypes.xsd.XSDDateTime ;
import org.apache.jena.enhanced.BuiltinPersonalities ;
import org.apache.jena.enhanced.EnhGraph ;
import org.apache.jena.enhanced.Personality ;
import org.apache.jena.graph.* ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.* ;
import org.apache.jena.shared.impl.JenaParameters;
import org.apache.jena.sys.JenaSystem ;
import org.apache.jena.util.CollectionFactory ;
import org.apache.jena.util.SplitIRI;
import org.apache.jena.util.iterator.ClosableIterator;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.util.iterator.FilterIterator;
import org.apache.jena.util.iterator.Map1Iterator;
import org.apache.jena.vocabulary.RDF;

/** Common methods for model implementations.
 *
 * <P>This class implements common methods, mainly convenience methods, for
 *    model implementations.  It is intended use is as a base class from which
 *    model implementations can be derived.</P>
 */

public class ModelCom extends EnhGraph implements Model, PrefixMapping, Lock
{
    private static RDFReaderF readerFactory = new RDFReaderFImpl();
    private static RDFWriterF writerFactory = new RDFWriterFImpl();
    private Lock modelLock = null;

    static {
        // This forces RIOT (in ARQ) to initialize but after Jena readers/writers
        // have cleanly initialized from the calls of RDFReaderFImpl and RDFWriterFImpl
        // above.  RIOT initialization happens before model.read can be called.
        JenaSystem.init();
    }

    /**
     * make a model based on the specified graph
     */
    public ModelCom(Graph base) {
        this(base, BuiltinPersonalities.model);
    }

    public ModelCom(Graph base, Personality<RDFNode> personality) {
        super(base, personality);
    }

    @Override
    public Graph getGraph()
    { return graph; }

    protected static Model createWorkModel()
    { return ModelFactory.createDefaultModel(); }

    @Override
    public RDFNode asRDFNode( Node n ) {
        return n.isLiteral()
            ? (RDFNode) this.getNodeAs( n, Literal.class )
            : (RDFNode) this.getNodeAs( n, Resource.class );
    }

    @Override
    public Resource wrapAsResource( Node n ) {
        if (n.isLiteral())
            throw new UnsupportedOperationException( "literal cannot be converted to Resource" );
        return this.getNodeAs( n, Resource.class );
    }

    @Override
    public Model addLiteral( Resource s, Property p, boolean o )
    { return add(s, p, createTypedLiteral( o ) ); }

    @Override
    public Model addLiteral( Resource s, Property p, long o )
    { return add(s, p, createTypedLiteral( o ) ); }

    @Override
    public Model addLiteral( Resource s, Property p, int o )
    { return add(s, p, createTypedLiteral( o ) ); }

    @Override
    public Model addLiteral( Resource s, Property p, char o )
    { return add(s, p, createTypedLiteral( o ) ); }

    @Override
    public Model addLiteral( Resource s, Property p, float o )
    { return add( s, p, createTypedLiteral( o ) ); }

    @Override
    public Model addLiteral( Resource s, Property p, double o )
    { return add(s, p, createTypedLiteral( o ) ); }

    @Override
    public Model add(Resource s, Property p, String o)  {
        return add( s, p, literal( o ) );
    }

    @Override
    public Model add(Resource s, Property p, String lex, RDFDatatype datatype) {
        add(s, p, literal(lex, datatype));
        return this;
    }

    private Literal literal( String s )
    { return new LiteralImpl( NodeFactory.createLiteralString(s), this ); }

    private Literal literal( String s, String lang)
    { return new LiteralImpl( NodeFactory.createLiteralLang( s, lang), this ); }

    private Literal literal( String s, String lang, String textDirection)
    { return new LiteralImpl( NodeFactory.createLiteralDirLang( s, lang, textDirection), this ); }

    private Literal literal( String lex, RDFDatatype datatype)
    { return new LiteralImpl( NodeFactory.createLiteralDT( lex, datatype), this ); }

    @Override
    public Model add( Resource s, Property p, String o, String l )
    { return add( s, p, literal(o, l) ); }

    @Override
    public Model add( Resource s, Property p, String o, String l, String d)
    { return add( s, p, literal(o, l, d) ); }

    @Override
    public Model addLiteral( Resource s, Property p, Literal o )
    { return add( s, p, o ); }

    private RDFNode asObject( Object o )
    { return o instanceof RDFNode ? (RDFNode) o : createTypedLiteral( o ); }

    @Override
    public Model add( StmtIterator iter )  {
        try { GraphUtil.add( getGraph(), asTriples( iter ) ); }
        finally { iter.close(); }
        return this;
    }

    @Override
    public Model add(Model m) {
        GraphUtil.addInto(getGraph(), m.getGraph());
        return this;
    }

    @SuppressWarnings("deprecation")
    @Override
    public RDFReaderI getReader(String lang)  {
        return readerFactory.getReader(lang);
    }

    @Override
    public Model read(String url)  {
        readerFactory .getReader(null) .read(this, url);
        return this;
    }

    @Override
    public Model read(Reader reader, String base)  {
        readerFactory .getReader(null) .read(this, reader, base);
        return this;
    }

    @Override
    public Model read(InputStream reader, String base)  {
        readerFactory .getReader(null) .read(this, reader, base);
        return this;
    }

    @Override
    public Model read(String url, String lang)  {
        readerFactory. getReader(lang) .read(this, url);
        return this;
    }

    @Override
    public Model read(String url, String base, String lang) {
        try (InputStream in = new URI(url).toURL().openStream()) {
            read(in, base, lang);
        } catch (IOException  e) {
            throw new WrappedIOException(e);
        } catch (URISyntaxException e) {
            throw new JenaException(e);
        }
        return this;
    }

    @Override
    public Model read(Reader reader, String base, String lang) {
        readerFactory.getReader(lang).read(this, reader, base);
        return this;
    }

    @Override
    public Model read(InputStream reader, String base, String lang) {
        readerFactory.getReader(lang).read(this, reader, base);
        return this;
    }

    /**
     * Get the model's writer after priming it with the model's namespace prefixes.
     */
    @SuppressWarnings("deprecation")
    @Override
    public RDFWriterI getWriter(String lang) {
        return writerFactory.getWriter(lang);
    }

    @Override
    public Model write(Writer writer, String lang) {
        getWriter(lang).write(this, writer, "");
        return this;
    }

    @Override
    public Model write(Writer writer, String lang, String base) {
        getWriter(lang).write(this, writer, base);
        return this;
    }

    @Override
    public Model write(OutputStream writer, String lang) {
        getWriter(lang).write(this, writer, "");
        return this;
    }

    @Override
    public Model write(OutputStream writer, String lang, String base) {
        getWriter(lang).write(this, writer, base);
        return this;
    }

    @Override
    public Model remove(Statement s) {
        graph.delete(s.asTriple());
        return this;
    }

    @Override
    public Model remove(Resource s, Property p, RDFNode o) {
        graph.delete(Triple.create(s.asNode(), p.asNode(), o.asNode()));
        return this;
    }

    @Override
    public Model remove(StmtIterator iter) {
        GraphUtil.delete(getGraph(), asTriples(iter));
        return this;
    }

    @Override
    public Model remove(Model m) {
        GraphUtil.deleteFrom(getGraph(), m.getGraph());
        return this;
    }

    @Override
    public Model removeAll() {
        getGraph().clear();
        return this;
    }

    @Override
    public Model removeAll(Resource s, Property p, RDFNode o) {
        getGraph().remove(asNode(s), asNode(p), asNode(o));
        return this;
    }

    @Override
    public boolean containsLiteral( Resource s, Property p, boolean o )
    { return contains(s, p, createTypedLiteral( o ) ); }

    @Override
    public boolean containsLiteral( Resource s, Property p, long o )
    { return contains(s, p, createTypedLiteral( o ) ); }

    @Override
    public boolean containsLiteral( Resource s, Property p, int o )
    { return contains(s, p, createTypedLiteral( o ) ); }

    @Override
    public boolean containsLiteral( Resource s, Property p, char o )
    { return contains(s, p, createTypedLiteral( o ) ); }

    @Override
    public boolean containsLiteral( Resource s, Property p, float o )
    { return contains(s, p, createTypedLiteral( o ) ); }

    @Override
    public boolean containsLiteral( Resource s, Property p, double o )
    { return contains(s, p, createTypedLiteral( o ) ); }

    @Override
    public boolean contains( Resource s, Property p, String o )
    { return contains( s, p, o, "" ); }

    @Override
    public boolean contains( Resource s, Property p, String o, String l )
    { return contains( s, p, literal( o, l ) ); }

    @Override
    public boolean contains( Resource s, Property p, String o, String l, String d)
    { return contains( s, p, literal( o, l, d ) ); }

    @Override
    public boolean containsLiteral(Resource s, Property p, Object o)
    { return contains( s, p, asObject( o ) ); }

    @Override
    public boolean containsAny( Model model )
    { return containsAnyThenClose( model.listStatements() ); }

    @Override
    public boolean containsAll( Model model )
    { return containsAllThenClose( model.listStatements() ); }

    protected boolean containsAnyThenClose( StmtIterator iter )
    { try { return containsAny( iter ); } finally { iter.close(); } }

    protected boolean containsAllThenClose( StmtIterator iter )
    { try { return containsAll( iter ); } finally { iter.close(); } }

    @Override
    public boolean containsAny(StmtIterator iter) {
        while (iter.hasNext())
            if ( contains(iter.nextStatement()) )
                return true;
        return false;
    }

    @Override
    public boolean containsAll(StmtIterator iter) {
        while (iter.hasNext())
            if ( !contains(iter.nextStatement()) )
                return false;
        return true;
    }

    protected StmtIterator listStatements(Resource S, Property P, Node O) {
        return IteratorFactory.asStmtIterator(graph.find(asNode(S), asNode(P), O), this);
    }

    @Override
    public StmtIterator listStatements( Resource S, Property P, RDFNode O )
    { return listStatements( S, P, asNode( O ) ); }

    @Override
    public StmtIterator listStatements( Resource S, Property P, String O ) {
        return O == null ? listStatements(S, P, Node.ANY)
                         :  listStatements( S, P, NodeFactory.createLiteralString( O ) );
    }

    @Override
    public StmtIterator listStatements( Resource S, Property P, String O, String L ) {
        if (O != null) {
            // this is not OK when L is null: returns only the statements whose lang is ""
            // return listStatements( S, P, Node.createLiteral( O, L, false ) );
            if (L != null) return listStatements( S, P, NodeFactory.createLiteralLang( O, L ) );
            // there's maybe a better way
            return new StringFilteredStmtIterator(O, listStatements(S, P, Node.ANY));
        } else {
            return new LangFilteredStmtIterator(L, listStatements(S, P, Node.ANY));
        }
    }

    @Override
    public StmtIterator listStatements( Resource S, Property P, String O, String L, String D ) {
        if (O != null) {
            // this is not OK when L is null: returns only the statements whose lang is ""
            // return listStatements( S, P, Node.createLiteral( O, L, false ) );
            if (L != null) {
                if ( D != null )
                    return listStatements( S, P, NodeFactory.createLiteralDirLang( O, L, D ) );
                else
                    return listStatements( S, P, NodeFactory.createLiteralLang( O, L ) );
            }
            // there's maybe a better way
            return new StringFilteredStmtIterator(O, listStatements(S, P, Node.ANY));
        } else {
            return new LangFilteredStmtIterator(L, listStatements(S, P, Node.ANY));
        }
    }

	private class StringFilteredStmtIterator extends FilterIterator<Statement> implements StmtIterator {
		public StringFilteredStmtIterator(final String str, Iterator<Statement> it) {
			super(s -> {
				RDFNode o = s.getObject();
				if (o instanceof Literal) {
					if (str == null)
						return true; // should not happen
				return (str.equals(o.asLiteral().getString()));
			}
			return false;
		}, it);
		}
        @Override public Statement nextStatement() { return next(); }
    }

    private class LangFilteredStmtIterator extends FilterIterator<Statement> implements StmtIterator {
		public LangFilteredStmtIterator(final String l, Iterator<Statement> it) {
			super(s -> {
				RDFNode o = s.getObject();
				if (o instanceof Literal) {
					if (l == null)
						return true;
					return (l.equals(o.asLiteral().getLanguage()));
				}
				return false;
			}, it);
		}
        @Override public Statement nextStatement() { return next(); }
    }


    @Override
    public StmtIterator listLiteralStatements( Resource S, Property P, boolean O )
    { return listStatements( S, P, createTypedLiteral( O ) ); }

    @Override
    public StmtIterator listLiteralStatements( Resource S, Property P, long O )
    { return listStatements( S, P, createTypedLiteral( O ) ); }

    @Override
    public StmtIterator listLiteralStatements( Resource S, Property P, int O )
    { return listStatements( S, P, createTypedLiteral( O ) ); }

    @Override
    public StmtIterator listLiteralStatements( Resource S, Property P, char  O )
    { return listStatements( S, P, createTypedLiteral( O ) ); }

    @Override
    public StmtIterator listLiteralStatements( Resource S, Property P, float O )
    { return listStatements( S, P, createTypedLiteral( O ) ); }

    @Override
    public StmtIterator listLiteralStatements( Resource S, Property P, double  O )
    { return listStatements( S, P, createTypedLiteral( O ) ); }

    @Override
    public ResIterator listResourcesWithProperty( Property p, boolean o )
    { return listResourcesWithProperty(p, createTypedLiteral( o ) ); }

    @Override
    public ResIterator listResourcesWithProperty( Property p, char o )
    { return listResourcesWithProperty(p, createTypedLiteral( o ) ); }

    @Override
    public ResIterator listResourcesWithProperty( Property p, long o )
    { return listResourcesWithProperty(p, createTypedLiteral( o ) ); }

    @Override
    public ResIterator listResourcesWithProperty( Property p, float o )
    { return listResourcesWithProperty(p, createTypedLiteral( o ) ); }

    @Override
    public ResIterator listResourcesWithProperty( Property p, double o )
    { return listResourcesWithProperty(p, createTypedLiteral( o ) ); }

    @Override
    public ResIterator listResourcesWithProperty( Property p, Object o )
    { return listResourcesWithProperty( p, createTypedLiteral( o ) ); }

    @Override
    public ResIterator listSubjectsWithProperty( Property p, RDFNode o )
    { return listResourcesWithProperty( p, o ); }

    @Override
    public ResIterator listSubjectsWithProperty( Property p, String o )
    { return listSubjectsWithProperty( p, literal(o) ); }

    @Override
    public ResIterator listSubjectsWithProperty( Property p, String o, String l )
    { return listResourcesWithProperty(p, literal( o, l ) ); }

    @Override
    public ResIterator listSubjectsWithProperty( Property p, String o, String l, String d)
    { return listResourcesWithProperty(p, literal( o, l, d ) ); }

    @Override
    public Resource createResource( Resource type )
    { return createResource().addProperty( RDF.type, type ); }

    @Override
    public Resource createResource( String uri,Resource type )
    { return getResource( uri ).addProperty( RDF.type, type ); }

    @Override
    public StatementTerm createStatementTerm( Statement statement )
    { return new StatementTermImpl( statement, this ); }

    /** create a type literal from a boolean value.
     *
     * <p> The value is converted to a string using its <CODE>toString</CODE>
     * method. </p>
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral( boolean v )  {
        return createTypedLiteral( Boolean.valueOf( v ) );
    }

    /** create a typed literal from an integer value.
     *
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral(int v) {
        return createTypedLiteral(Integer.valueOf(v));
    }

    /** create a typed literal from a long integer value.
     *
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral(long v) {
        return createTypedLiteral(Long.valueOf(v));
    }

    /** create a typed literal from a char value.
     *
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral(char v) {
        return createTypedLiteral(Character.valueOf(v));
    }

    /** create a typed literal from a float value.
     *
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral(float v) {
        return createTypedLiteral(Float.valueOf(v));
    }

    /** create a typed literal from a double value.
     *
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral(double v)  {
        return createTypedLiteral(Double.valueOf(v));
    }

    /** create a typed literal from a String value.
     *
     * @param v the value of the literal
     *
     * @return a new literal representing the value v
     */
    @Override
    public Literal createTypedLiteral(String v)  {
        return new LiteralImpl(NodeFactory.createLiteralString(v), this);
    }

    /**
     * Create a typed literal xsd:dateTime from a Calendar object.
     */
    @Override
    public Literal createTypedLiteral(Calendar cal) {
        Object value = new XSDDateTime(cal);
        Node n = NodeFactory.createLiteralByValue(value, XSDDatatype.XSDdateTime);
        return new LiteralImpl(n, this);
    }

    /**
     * Build a typed literal from its lexical form. The
     * lexical form will be parsed now and the value stored. If
     * the form is not legal this will throw an exception.
     *
     * @param lex the lexical form of the literal
     * @param dtype the type of the literal, null for old style "plain" literals
     * @throws DatatypeFormatException if lex is not a legal form of dtype
     */
    @Override
    public Literal createTypedLiteral(String lex, RDFDatatype dtype) throws DatatypeFormatException {
        Node n = NodeFactory.createLiteralDT( lex, dtype );
        // Force value to be calculated if it was delayed.
        // Check here as well because NodeFactory may change to be being "lazy value".
        if ( JenaParameters.enableEagerLiteralValidation ) {
            Object v = n.getLiteralValue();
            if ( v == null )
                throw new DatatypeFormatException("Bad lexical form '"+lex+"'for datatype: "+dtype);
        }
        return new LiteralImpl( n, this);
    }

    /**
     * Build a typed literal from its value form.
     *
     * @param value the value of the literal
     * @param dtype the type of the literal, null for old style "plain" literals
     */
    @Override
    public Literal createTypedLiteral(Object value, RDFDatatype dtype) {
        Node n = NodeFactory.createLiteralByValue(value, dtype);
        return new LiteralImpl( n, this );
    }

    /**
     * Build a typed literal from its lexical form. The
     * lexical form will be parsed now and the value stored. If
     * the form is not legal this will throw an exception.
     *
     * @param lex the lexical form of the literal
     * @param typeURI the uri of the type of the literal, null for old style "plain" literals
     * @throws DatatypeFormatException if lex is not a legal form of dtype
     */
    @Override
    public Literal createTypedLiteral(String lex, String typeURI) {
        RDFDatatype dt = TypeMapper.getInstance().getSafeTypeByName(typeURI);
        Node n = NodeFactory.createLiteralDT(lex, dt);
        // Force value to be calculated if it was delayed.
        // Check here as well because NodeFactory may change to be being "lazy value".
        if ( JenaParameters.enableEagerLiteralValidation ) {
            Object v = n.getLiteralValue();
            if ( v == null )
                throw new DatatypeFormatException("Bad lexical form '"+lex+"'for datatype: "+typeURI);
        }
        return new LiteralImpl( n, this );
    }

    /**
     * Build a typed literal from its value form.
     *
     * @param value the value of the literal
     * @param typeURI the URI of the type of the literal, null for old style "plain" literals
     */
    @Override
    public Literal createTypedLiteral(Object value, String typeURI) {
        RDFDatatype dt = TypeMapper.getInstance().getSafeTypeByName(typeURI);
        Node n = NodeFactory.createLiteralByValue(value, dt);
        return new LiteralImpl( n, this );
    }

    /**
     * Build a typed literal label from its value form using
     * whatever datatype is currently registered as the the default
     * representation for this java class. No language tag is supplied.
     * @param value the literal value to encapsulate
     */
    @Override
    public Literal createTypedLiteral( Object value ) {
        // Catch special case of a Calendar which we want to act as if it were an XSDDateTime
        if (value instanceof Calendar)
            return createTypedLiteral( (Calendar)value );
        Node n = NodeFactory.createLiteralByValue(value);
        return new LiteralImpl( n, this );
    }

    @Override
    public Literal createLiteral( String v )
    { return createLiteral( v, "" ); }

    @Override
    public Literal createLiteral( String v, String l )
    { return literal( v, l ); }

    @Override
    public Literal createLiteral( String v, String l, String d)
    { return literal( v, l, d ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, boolean o )
    { return createStatement( r, p, createTypedLiteral( o ) ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, long o )
    { return createStatement( r, p, createTypedLiteral( o ) ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, int o )
    { return createStatement( r, p, createTypedLiteral( o ) ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, char o )
    { return createStatement( r, p, createTypedLiteral( o ) ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, float o )
    { return createStatement( r, p, createTypedLiteral( o ) ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, double o )
    { return createStatement( r, p, createTypedLiteral( o ) ); }

    @Override
    public Statement createStatement( Resource r, Property p, String o )
    { return createStatement( r, p, createLiteral( o ) ); }

    @Override
    public Statement createLiteralStatement( Resource r, Property p, Object o )
    { return createStatement( r, p, asObject( o ) ); }

    @Override
    public Statement createStatement(Resource r, Property p, String o, String l)
    { return createStatement( r, p, literal(o, l)); }

    @Override
    public Bag createBag()
    { return createBag( null ); }

    @Override
    public Alt createAlt()
    { return createAlt( null ); }

    @Override
    public Seq createSeq()
    { return createSeq( null ); }

    /**
        Answer a (the) new empty list
        Until this is made the object or subject in the model, it will not appear in a written form.
        @return An RDF-encoded list of no elements (ie nil)
     */
    @Override
    public RDFList createList()
    { return getResource( RDF.nil.getURI() ).as( RDFList.class ); }

    /**
     * <p>Answer a new list containing the resources from the given iterator, in order.</p>
     * @param members An iterator, each value of which is expected to be an RDFNode.
     * @return An RDF-encoded list of the elements of the iterator
     */
    @Override
    public RDFList createList(Iterator<? extends RDFNode> members) {
        if ( !members.hasNext() )
            return createList();

        Resource root = createResource().addProperty(RDF.first, members.next());
        Resource last = root;
        while (members.hasNext()) {
            Resource rest = createResource().addProperty(RDF.first, members.next());
            last.addProperty(RDF.rest, rest);
            last = rest;
        }
        last.addProperty(RDF.rest, RDF.nil);

        return root.as(RDFList.class);
    }

    /**
     * <p>Answer a new list containing the RDF nodes from the given array, in order</p>
     * @param members An array of RDFNodes that will be the members of the list
     * @return An RDF-encoded list
     */
    @Override
    public RDFList createList( RDFNode... members ) {
        return createList( Arrays.asList( members ).iterator() );
    }

    @Override
    public RDFNode getRDFNode( Node n )
    { return asRDFNode( n ); }

    @Override
    public Resource getResource( String uri )
    { return IteratorFactory.asResource(makeURI(uri),this); }

    @Override
    public Resource getResource( AnonId id )
    { return new ResourceImpl( id, this ); }

    @Override
    public Property getProperty(String uri) {
        if ( uri == null )
            throw new InvalidPropertyURIException(null);
        return IteratorFactory.asProperty(makeURI(uri), this);
    }

    @Override
    public Property getProperty( String nameSpace,String localName )
    { return getProperty( nameSpace + localName ); }

    @Override
    public Seq getSeq( String uri )
    { return (Seq) IteratorFactory.asResource( makeURI( uri ),Seq.class, this); }

    @Override
    public Seq getSeq( Resource r )
    { return r.inModel( this ).as( Seq.class ); }

    @Override
    public Bag getBag( String uri )
    { return (Bag) IteratorFactory.asResource( makeURI( uri ),Bag.class, this ); }

    @Override
    public Bag getBag( Resource r )
    { return r.inModel( this ).as( Bag.class ); }

    @Override
    public RDFList getList( String uri )
    { return (RDFList)IteratorFactory.asResource( makeURI( uri ), RDFList.class, this ); }

    @Override
    public RDFList getList( Resource r )
    { return r.inModel( this ).as( RDFList.class ); }

    static private Node makeURI(String uri)
    { return uri == null ? NodeFactory.createBlankNode() : NodeFactory.createURI( uri ); }

    @Override
    public Alt getAlt( String uri )
    { return (Alt) IteratorFactory.asResource( makeURI(uri) ,Alt.class, this ); }

    @Override
    public Alt getAlt( Resource r )
    { return r.inModel( this ).as( Alt.class ); }

    @Override
    public long size()
    { return graph.size(); }

    @Override
    public boolean isEmpty()
    { return graph.isEmpty(); }

    private void updateNamespace(Set<String> set, Iterator<Node> it) {
        while (it.hasNext()) {
            Node node = it.next();
            if ( node.isURI() ) {
                String uri = node.getURI();
                String ns = uri.substring(0,  SplitIRI.splitXML(uri));
                // String ns = IteratorFactory.asResource( node, this
                // ).getNameSpace();
                set.add(ns);
            }
        }
    }

    private ExtendedIterator<Node> listPredicates()
    { return GraphUtil.listPredicates(graph, Node.ANY, Node.ANY ); }

    private Iterator<Node> listTypes() {
        Set<Node> types = CollectionFactory.createHashedSet();
        ClosableIterator<Triple> it = graph.find(null, RDF.type.asNode(), null);
        while (it.hasNext())
            types.add(it.next().getObject());
        return types.iterator();
    }

    @Override
    public NsIterator listNameSpaces() {
        Set<String> nameSpaces = CollectionFactory.createHashedSet();
        updateNamespace(nameSpaces, listPredicates());
        updateNamespace(nameSpaces, listTypes());
        return new NsIteratorImpl(nameSpaces.iterator(), nameSpaces);
    }

    private PrefixMapping getPrefixMapping()
    { return getGraph().getPrefixMapping(); }

    @Override
    public boolean samePrefixMappingAs( PrefixMapping other )
    { return getPrefixMapping().samePrefixMappingAs( other ); }

    @Override
    public PrefixMapping lock() {
        getPrefixMapping().lock();
        return this;
    }

    @Override
    public Model setNsPrefix(String prefix, String uri) {
        getPrefixMapping().setNsPrefix(prefix, uri);
        return this;
    }

    @Override
    public Model removeNsPrefix(String prefix) {
        getPrefixMapping().removeNsPrefix(prefix);
        return this;
    }

    @Override
    public Model clearNsPrefixMap() {
        getPrefixMapping().clearNsPrefixMap();
        return this;
    }

    @Override
    public Model setNsPrefixes(PrefixMapping pm) {
        getPrefixMapping().setNsPrefixes(pm);
        return this;
    }

    @Override
    public Model setNsPrefixes(Map<String, String> map) {
        getPrefixMapping().setNsPrefixes(map);
        return this;
    }

    @Override
    public Model withDefaultMappings(PrefixMapping other) {
        getPrefixMapping().withDefaultMappings(other);
        return this;
    }

    @Override
    public String getNsPrefixURI( String prefix )
    { return getPrefixMapping().getNsPrefixURI( prefix ); }

    @Override
    public String getNsURIPrefix( String uri )
    { return getPrefixMapping().getNsURIPrefix( uri ); }

    @Override
    public Map<String, String> getNsPrefixMap()
    { return getPrefixMapping().getNsPrefixMap(); }

    @Override
    public String expandPrefix( String prefixed )
    { return getPrefixMapping().expandPrefix( prefixed ); }

    @Override
    public String qnameFor( String uri )
    { return getPrefixMapping().qnameFor( uri ); }

    @Override
    public String shortForm( String uri )
    { return getPrefixMapping().shortForm( uri ); }

    @Override
    public boolean hasNoMappings()
    { return getPrefixMapping().hasNoMappings(); }

    @Override
    public int numPrefixes()
    { return getPrefixMapping().numPrefixes(); }

    /**
     * Service method to update the namespaces of a Model given the mappings from
     * prefix names to sets of URIs. If the prefix maps to multiple URIs, then we
     * discard it completely.
     *
     * @param m Model whose namespace is to be updated
     * @param ns the namespace map to add to the Model
     */
    public static void addNamespaces(Model m, Map<String, Set<String>> ns) {
        PrefixMapping pm = m;
        for ( Map.Entry<String, Set<String>> e : ns.entrySet() ) {
            String key = e.getKey();
            Set<String> values = e.getValue();
            Set<String> niceValues = CollectionFactory.createHashedSet();
            for ( String uri : values ) {
                niceValues.add(uri);
            }
            if ( niceValues.size() == 1 ) {
                pm.setNsPrefix(key, niceValues.iterator().next());
            }
        }
    }

    @Override
    public StmtIterator listStatements()
    { return IteratorFactory.asStmtIterator( GraphUtil.findAll( graph ), this); }

    /**
     * add a Statement to this Model by adding its SPO components.
     */
    @Override
    public Model add(Statement s) {
        add(s.getSubject(), s.getPredicate(), s.getObject());
        return this;
    }

    /**
        Add all the statements to the model by converting them to an array of corresponding
        triples and removing those from the underlying graph.
     */
    @Override
    public Model add(Statement[] statements) {
        GraphUtil.add(getGraph(), StatementImpl.asTriples(statements));
        return this;
    }

    /**
     * Add all the statements to the model by converting the list to an array of
     * Statement and removing that.
     */
    @Override
    public Model add(List<Statement> statements) {
        GraphUtil.add(getGraph(), asTriples(statements));
        return this;
    }

    private List<Triple> asTriples(List<Statement> statements) {
        List<Triple> L = new ArrayList<>(statements.size());
        for ( Statement statement : statements ) {
            L.add(statement.asTriple());
        }
        return L;
    }

    private Iterator<Triple> asTriples( StmtIterator it )
    { return it.mapWith( s -> s.asTriple() ); }

    /**
     * remove all the Statements from the model by converting them to triples and
     * removing those triples from the underlying graph.
     */
    @Override
    public Model remove(Statement[] statements) {
        GraphUtil.delete(getGraph(), StatementImpl.asTriples(statements));
        return this;
    }

    /**
     * Remove all the Statements from the model by converting the List to a
     * List(Statement) and removing that.
     */
    @Override
    public Model remove(List<Statement> statements) {
        GraphUtil.delete(getGraph(), asTriples(statements));
        return this;
    }

    @Override
    public Model add( Resource s, Property p, RDFNode o )  {
        graph.add( Triple.create( s.asNode(), p.asNode(), o.asNode() ) );
        return this;
    }

    @Override
    public boolean contains( Statement s )
    { return graph.contains( s.asTriple() ); }

    @Override
    public boolean containsResource( RDFNode r )
    { return GraphUtil.containsNode( graph, r.asNode() ); }

    @Override
    public boolean contains( Resource s, Property p )
    { return contains( s, p, (RDFNode) null );  }

    @Override
    public boolean contains( Resource s, Property p, RDFNode o )
    { return graph.contains( asNode( s ), asNode( p ), asNode( o ) ); }

    @Override
    public Statement getRequiredProperty(Resource s, Property p) {
        Statement st = getProperty( s, p );
        if (st == null) throw new PropertyNotFoundException( p );
        return st;
    }

    @Override
    public Statement getRequiredProperty(Resource s, Property p, String lang) {
        Statement st = getProperty( s, p , lang );
        if (st == null) throw new PropertyNotFoundException( p );
        return st;
    }

    @Override
    public Statement getProperty( Resource s, Property p ) {
        StmtIterator iter = listStatements( s, p, (RDFNode) null );
        try { return iter.hasNext() ? iter.nextStatement() : null; }
        finally { iter.close(); }
    }

    @Override
    public Statement getProperty( Resource s, Property p, String lang) {
        StmtIterator iter = listStatements( s, p, null, lang );
        try { return iter.hasNext() ? iter.nextStatement() : null; }
        finally { iter.close(); }
    }

    public static Node asNode( RDFNode x )
    { return x == null ? Node.ANY : x.asNode(); }

    private NodeIterator listObjectsFor( RDFNode s, RDFNode p ) {
        ClosableIterator<Node> xit = GraphUtil.listObjects(graph, asNode( s ), asNode( p ) );
        return IteratorFactory.asRDFNodeIterator( xit, this );
    }

    private ResIterator listSubjectsFor(RDFNode p, RDFNode o) {
        ClosableIterator<Node> xit = GraphUtil.listSubjects(graph, asNode(p), asNode(o));
        return IteratorFactory.asResIterator(xit, this);
    }

    @Override
    public ResIterator listSubjects()
    { return listSubjectsFor( null, null ); }

    @Override
    public ResIterator listResourcesWithProperty(Property p)
    { return listSubjectsFor( p, null ); }

    @Override
    public ResIterator listSubjectsWithProperty(Property p)
    { return listResourcesWithProperty( p ); }

    @Override
    public ResIterator listResourcesWithProperty(Property p, RDFNode o)
    { return listSubjectsFor( p, o ); }

    @Override
    public NodeIterator listObjects()
    { return listObjectsFor( null, null ); }

    @Override
    public NodeIterator listObjectsOfProperty(Property p)
    { return listObjectsFor( null, p ); }

    @Override
    public NodeIterator listObjectsOfProperty(Resource s, Property p)
    { return listObjectsFor( s, p ); }

    @Override
    public boolean supportsTransactions()
    { return getTransactionHandler().transactionsSupported(); }

    @Override
    public Model begin()
    { getTransactionHandler().begin(); return this; }

    @Override
    public Model abort()
    { getTransactionHandler().abort(); return this; }

    @Override
    public Model commit()
    { getTransactionHandler().commit(); return this; }

    private TransactionHandler getTransactionHandler()
    { return getGraph().getTransactionHandler(); }

    @Override
    public void executeInTxn( Runnable action )
    { getTransactionHandler().execute( action ); }

    /**
     * Execute the supplier <code>action</code> within a transaction. If it completes normally,
     * commit the transaction and return the result, otherwise abort the transaction.
     */
    @Override
    public <T> T calculateInTxn( Supplier<T> action )
    { return getTransactionHandler().calculate( action ); }

    @Override
    public boolean independent()
    { return true; }

    @Override
    public Resource createResource()
    { return IteratorFactory.asResource( NodeFactory.createBlankNode(),this ); }

    @Override
    public Resource createResource( String uri )
    { return getResource( uri ); }

    @Override
    public Resource createResource( AnonId anonId )
    { return getResource( anonId ); }

    @Override
    public Property createProperty( String uri )
    { return getProperty( uri ); }

    @Override
    public Property createProperty(String nameSpace, String localName)
    { return getProperty(nameSpace, localName); }

    /**
     * create a Statement from the given r, p, and o.
     */
    @Override
    public Statement createStatement(Resource r, Property p, RDFNode o)
    { return new StatementImpl( r, p, o, this ); }

    @Override
    public Bag createBag(String uri)
    { return (Bag) getBag(uri).addProperty( RDF.type, RDF.Bag ); }

    @Override
    public Alt createAlt( String uri )
    { return (Alt) getAlt(uri).addProperty( RDF.type, RDF.Alt ); }

    @Override
    public Seq createSeq(String uri)
    { return (Seq) getSeq(uri).addProperty( RDF.type, RDF.Seq ); }

    /**
     * Answer a Statement in this Model which encodes the given Triple.
     *
     * @param t a triple to wrap as a statement
     * @return a statement wrapping the triple and in this model
     */
    @Override
    public Statement asStatement( Triple t )
    { return StatementImpl.toStatement( t, this ); }

    public Statement[] asStatements(Triple[] triples) {
        Statement [] result = new Statement [triples.length];
        for (int i = 0; i < triples.length; i += 1) result[i] = asStatement( triples[i] );
        return result;
    }

    public List<Statement> asStatements(List<Triple> triples) {
        List<Statement> L = new ArrayList<>(triples.size());
        for ( Triple triple : triples ) {
            L.add(asStatement(triple));
        }
        return L;
    }

    public Model asModel( Graph g )
    { return new ModelCom( g ); }

    public StmtIterator asStatements( final Iterator<Triple> it )
    { return new StmtIteratorImpl( new Map1Iterator<>( t -> asStatement( t ), it ) ); }

    public StmtIterator listBySubject( Container cont )
    { return listStatements( cont, null, (RDFNode) null ); }

    @Override
    public void close()
    { graph.close(); }

    @Override
    public boolean isClosed()
    { return graph.isClosed(); }

    @Override
    public boolean supportsSetOperations()
    {return true;}

    @Override
    public Model union( Model model )
    { return createWorkModel() .add(this) .add( model ); }

    /**
     * Intersect this with another model. As an attempt at optimisation, we try and
     * ensure we iterate over the smaller model first. Nowadays it's not clear that
     * this is a good idea, since <code>size()</code> can be expensive on database
     * and inference models.
     *
     * @see org.apache.jena.rdf.model.Model#intersection(org.apache.jena.rdf.model.Model)
     */
    @Override
    public Model intersection( Model other )
    { return this.size() < other.size() ? intersect( this, other ) : intersect( other, this ); }

    /**
     * Answer a Model that is the intersection of the two argument models. The
     * first argument is the model iterated over, and the second argument is the
     * one used to check for membership. [So the first one should be "small" and
     * the second one "membership cheap".]
     */
    public static Model intersect(Model smaller, Model larger) {
        Model result = createWorkModel();
        StmtIterator it = smaller.listStatements();
        try { return addCommon( result, it, larger ); }
        finally { it.close(); }
    }

    /**
     * Answer the argument result with all the statements from the statement
     * iterator that are in the other model added to it.
     *
     * @param result the Model to add statements to and return
     * @param it an iterator over the candidate statements
     * @param other the model that must contain the statements to be added
     * @return result, after the suitable statements have been added to it
     */
    protected static Model addCommon(Model result, StmtIterator it, Model other) {
        while (it.hasNext()) {
            Statement s = it.nextStatement();
            if ( other.contains(s) )
                result.add(s);
        }
        return result;
    }

    @Override
    public Model difference(Model model) {
        Model resultModel = createWorkModel();
        StmtIterator iter = null;
        Statement stmt;
        try {
            iter = listStatements();
            while (iter.hasNext()) {
                stmt = iter.nextStatement();
                if (! model.contains(stmt)) {
                    resultModel.add(stmt);
                }
            }
            return resultModel;
        } finally {
            if (null != iter) {
                iter.close();
            }
        }
    }

    @Override
    public String toString()
    { return "<ModelCom  " + getGraph() + " | " + reifiedToString() + ">"; }

    public String reifiedToString()
    { return statementsToString( listStatements() ); }

    protected String statementsToString(StmtIterator it) {
        StringBuilder b = new StringBuilder();
        while (it.hasNext())
            b.append(" ").append(it.nextStatement());
        return b.toString();
    }

    /**
     * Answer whether or not these two graphs are isomorphic.
     */
    @Override
    public boolean isIsomorphicWith(Model m) {
        Graph L = this.getGraph();
        Graph R = m.getGraph();
        return L.isIsomorphicWith(R);
    }

    public synchronized Lock getModelLock() {
        if ( modelLock == null )
            modelLock = new LockMRSW();
        return modelLock;
    }

    @Override
    public synchronized Lock getLock() {
        return getModelLock();
    }

    @Override
    public void enterCriticalSection(boolean requestReadLock) {
        this.getModelLock().enterCriticalSection(requestReadLock);
    }

    @Override
    public void leaveCriticalSection() {
        this.getModelLock().leaveCriticalSection();
    }

    /**
     * Register the listener with this model by registering its GraphListener
     * adaption with the underlying Graph.
     *
     * @param listener A ModelChangedListener to register for model events
     * @return this model, for cascading
     */
    @Override
    public Model register(ModelChangedListener listener) {
        getGraph().getEventManager().register(adapt(listener));
        return this;
    }

    /**
     * Unregister the listener from this model by unregistering its GraphListener
     * adaption from the underlying Graph.
     *
     * @param listener A ModelChangedListener to unregister from model events
     * @return this model, for cascading
     */
    @Override
    public Model unregister(ModelChangedListener listener) {
        getGraph().getEventManager().unregister(adapt(listener));
        return this;
    }

    /**
     * Answer a GraphListener that, when fed graph-level update events, fires the
     * corresponding model-level event handlers in <code>L</code>.
     *
     * @see ModelListenerAdapter
     * @param L a model listener to be wrapped as a graph listener
     * @return a graph listener wrapping L
     */
    public GraphListener adapt( final ModelChangedListener L )
    { return new ModelListenerAdapter( this, L ); }

    @Override
    public Model notifyEvent(Object e) {
        getGraph().getEventManager().notifyEvent(getGraph(), e);
        return this;
    }
}
