MemValueFactory.java

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.sail.memory.model;

import javax.xml.datatype.XMLGregorianCalendar;

import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Triple;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.base.AbstractValueFactory;
import org.eclipse.rdf4j.model.base.CoreDatatype;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Literals;
import org.eclipse.rdf4j.model.util.URIUtil;
import org.eclipse.rdf4j.model.vocabulary.RDF;

/**
 * A factory for MemValue objects that keeps track of created objects to prevent the creation of duplicate objects,
 * minimizing memory usage as a result.
 *
 * @author Arjohn Kampman
 * @author David Huynh
 */
public class MemValueFactory extends AbstractValueFactory {

	/*------------*
	 * Attributes *
	 *------------*/

	/**
	 * Registry containing the set of MemURI objects as used by a MemoryStore. This registry enables the reuse of
	 * objects, minimizing the number of objects in main memory.
	 */
	private final WeakObjectRegistry<IRI, MemIRI> iriRegistry = new WeakObjectRegistry<>();

	/**
	 * Registry containing the set of MemTriple objects as used by a MemoryStore. This registry enables the reuse of
	 * objects, minimizing the number of objects in main memory.
	 */
	private final WeakObjectRegistry<Triple, MemTriple> tripleRegistry = new WeakObjectRegistry<>();

	/**
	 * Registry containing the set of MemBNode objects as used by a MemoryStore. This registry enables the reuse of
	 * objects, minimizing the number of objects in main memory.
	 */
	private final WeakObjectRegistry<BNode, MemBNode> bnodeRegistry = new WeakObjectRegistry<>();

	/**
	 * Registry containing the set of MemLiteral objects as used by a MemoryStore. This registry enables the reuse of
	 * objects, minimizing the number of objects in main memory.
	 */
	private final WeakObjectRegistry<Literal, MemLiteral> literalRegistry = new WeakObjectRegistry<>();

	/**
	 * Registry containing the set of namespce strings as used by MemURI objects in a MemoryStore. This registry enables
	 * the reuse of objects, minimizing the number of objects in main memory.
	 */
	private final WeakObjectRegistry<String, String> namespaceRegistry = new WeakObjectRegistry<>();

	/**
	 * A cache of the most common IRIs to improve lookup performance when users use our vocabularies (eg.
	 * {@link RDF#TYPE}).
	 */

//	private final Cache<Value, MemLiteral> literalCache = CacheBuilder.newBuilder().concurrencyLevel(Runtime.getRuntime().availableProcessors()).weakKeys().weakValues().initialCapacity(1000).maximumSize(1000).build();
//	private final Cache<Value, MemIRI> iriCache = CacheBuilder.newBuilder().concurrencyLevel(Runtime.getRuntime().availableProcessors()).weakKeys().weakValues().initialCapacity(1000).maximumSize(1000).build();
//	private final Cache<Value, MemBNode> bNodeCache = CacheBuilder.newBuilder().concurrencyLevel(Runtime.getRuntime().availableProcessors()).weakKeys().weakValues().initialCapacity(1000).maximumSize(1000).build();
//	private final Cache<Value, MemTriple> tripleCache = CacheBuilder.newBuilder().concurrencyLevel(Runtime.getRuntime().availableProcessors()).weakKeys().weakValues().initialCapacity(1000).maximumSize(1000).build();
	public MemValueFactory() {
	}

	public void clear() {
		iriRegistry.clear();
		tripleRegistry.clear();
		bnodeRegistry.clear();
		literalRegistry.clear();
		namespaceRegistry.clear();
	}

	/**
	 * Returns a previously created MemValue that is equal to the supplied value, or <var>null</var> if the supplied
	 * value is a new value or is equal to <var>null</var>.
	 *
	 * @param value The MemValue equivalent of the supplied value, or <var>null</var>.
	 * @return A previously created MemValue that is equal to <var>value</var>, or <var>null</var> if no such value
	 *         exists or if <var>value</var> is equal to <var>null</var>.
	 */
	public MemValue getMemValue(Value value) {
		if (value == null) {
			return null;
		} else if (value.isIRI()) {
			return getMemURI((IRI) value);
		} else if (value.isBNode()) {
			return getMemBNode((BNode) value);
		} else if (value.isTriple()) {
			return getMemTriple((Triple) value);
		} else if (value.isLiteral()) {
			return getMemLiteral((Literal) value);
		} else {
			throw new IllegalArgumentException("value is not a Resource or Literal: " + value);
		}
	}

	/**
	 * See getMemValue() for description.
	 */
	public MemResource getMemResource(Resource resource) {
		if (resource == null) {
			return null;
		} else if (resource.isIRI()) {
			return getMemURI((IRI) resource);
		} else if (resource.isBNode()) {
			return getMemBNode((BNode) resource);
		} else if (resource.isTriple()) {
			return getMemTriple((Triple) resource);
		} else {
			throw new IllegalArgumentException("resource is not a URI or BNode: " + resource);
		}
	}

	/**
	 * See getMemValue() for description.
	 */
	public MemIRI getMemURI(IRI uri) {
		if (uri == null) {
			return null;
		} else if (isOwnMemIRI(uri)) {
			return (MemIRI) uri;
		} else {
			return iriRegistry.get(uri);
		}
	}

	/**
	 * See getMemValue() for description.
	 */
	public MemBNode getMemBNode(BNode bnode) {
		if (bnode == null) {
			return null;
		} else if (isOwnMemBnode(bnode)) {
			return (MemBNode) bnode;
		} else {
			return bnodeRegistry.get(bnode);
		}
	}

	/**
	 * See getMemValue() for description.
	 */
	public MemLiteral getMemLiteral(Literal literal) {
		if (literal == null) {
			return null;
		} else if (isOwnMemLiteral(literal)) {
			return (MemLiteral) literal;
		} else {
			return literalRegistry.get(literal);
		}
	}

	private MemTriple getMemTriple(Triple triple) {
		if (triple == null) {
			return null;
		} else if (isOwnMemTriple(triple)) {
			return (MemTriple) triple;
		} else {
			return tripleRegistry.get(triple);
		}
	}

	/**
	 * Checks whether the supplied value is an instance of <var>MemValue</var> and whether it has been created by this
	 * MemValueFactory.
	 */

	private boolean isOwnMemBnode(BNode value) {
		return value instanceof MemBNode && ((MemBNode) value).getCreator() == this;
	}

	private boolean isOwnMemLiteral(Literal value) {
		return value instanceof MemLiteral && ((MemLiteral) value).getCreator() == this;
	}

	private boolean isOwnMemTriple(Triple value) {
		return value instanceof MemTriple && ((MemTriple) value).getCreator() == this;
	}

	private boolean isOwnMemIRI(IRI value) {
		return value instanceof MemIRI && ((MemIRI) value).getCreator() == this;
	}

	/**
	 * Gets all URIs that are managed by this value factory.
	 *
	 * @return An autocloseable iterator.
	 */
	public WeakObjectRegistry.AutoCloseableIterator<MemIRI> getMemIRIsIterator() {
		return iriRegistry.closeableIterator();
	}

	/**
	 * Gets all bnodes that are managed by this value factory.
	 *
	 * @return An autocloseable iterator.
	 */
	public WeakObjectRegistry.AutoCloseableIterator<MemBNode> getMemBNodesIterator() {
		return bnodeRegistry.closeableIterator();
	}

	/**
	 * Gets all literals that are managed by this value factory.
	 *
	 * @return An autocloseable iterator.
	 */
	public WeakObjectRegistry.AutoCloseableIterator<MemLiteral> getMemLiteralsIterator() {
		return literalRegistry.closeableIterator();
	}

	/**
	 * Gets or creates a MemValue for the supplied Value. If the factory already contains a MemValue object that is
	 * equivalent to the supplied value then this equivalent value will be returned. Otherwise a new MemValue will be
	 * created, stored for future calls and then returned.
	 *
	 * @param value A Resource or Literal.
	 * @return The existing or created MemValue.
	 */
	public MemValue getOrCreateMemValue(Value value) {
		if (value.isResource()) {
			return getOrCreateMemResource((Resource) value);
		} else if (value.isLiteral()) {
			return getOrCreateMemLiteral((Literal) value);
		} else {
			throw new IllegalArgumentException("value is not a Resource or Literal: " + value);
		}
	}

	/**
	 * See {@link #getOrCreateMemValue(Value)} for description.
	 */
	public MemResource getOrCreateMemResource(Resource resource) {
		if (resource.isIRI()) {
			return getOrCreateMemURI((IRI) resource);
		} else if (resource.isBNode()) {
			return getOrCreateMemBNode((BNode) resource);
		} else if (resource.isTriple()) {
			return getOrCreateMemTriple((Triple) resource);
		} else {
			throw new IllegalArgumentException("resource is not a URI or BNode: " + resource);
		}
	}

	/**
	 * See {@link #getOrCreateMemValue(Value)} for description.
	 */
	public MemIRI getOrCreateMemURI(IRI uri) {
		if (isOwnMemIRI(uri)) {
			return (MemIRI) uri;
		}

		return iriRegistry.getOrAdd(uri, () -> {

			String namespace = uri.getNamespace();

			String sharedNamespace = namespaceRegistry.getOrAdd(namespace, () -> namespace);

			// Create a MemURI and add it to the registry
			return new MemIRI(this, sharedNamespace, uri.getLocalName());
		});
	}

	/**
	 * See {@link #getOrCreateMemValue(Value)} for description.
	 */
	public MemBNode getOrCreateMemBNode(BNode bnode) {
		if (isOwnMemBnode(bnode)) {
			return (MemBNode) bnode;
		}
		return bnodeRegistry.getOrAdd(bnode, () -> new MemBNode(this, bnode.getID()));
	}

	/**
	 * See {@link #getOrCreateMemValue(Value)} for description.
	 */
	public MemLiteral getOrCreateMemLiteral(Literal literal) {
		if (isOwnMemLiteral(literal)) {
			return (MemLiteral) literal;
		}

		return literalRegistry.getOrAdd(literal, () -> {
			String label = literal.getLabel();
			CoreDatatype coreDatatype = literal.getCoreDatatype();
			IRI datatype = coreDatatype != CoreDatatype.NONE ? coreDatatype.getIri() : literal.getDatatype();

			if (Literals.isLanguageLiteral(literal)) {
				return new MemLiteral(this, label, literal.getLanguage().get());
			} else {
				try {
					if (coreDatatype.isXSDDatatype()) {
						if (((CoreDatatype.XSD) coreDatatype).isIntegerDatatype()) {
							return new IntegerMemLiteral(this, label, literal.integerValue(), coreDatatype);
						} else if (coreDatatype == CoreDatatype.XSD.DECIMAL) {
							return new DecimalMemLiteral(this, label, literal.decimalValue(), coreDatatype);
						} else if (coreDatatype == CoreDatatype.XSD.FLOAT) {
							return new NumericMemLiteral(this, label, literal.floatValue(), coreDatatype);
						} else if (coreDatatype == CoreDatatype.XSD.DOUBLE) {
							return new NumericMemLiteral(this, label, literal.doubleValue(), coreDatatype);
						} else if (coreDatatype == CoreDatatype.XSD.BOOLEAN) {
							return new BooleanMemLiteral(this, label, literal.booleanValue());
						} else if (coreDatatype == CoreDatatype.XSD.DATETIME) {
							return new CalendarMemLiteral(this, label, coreDatatype, literal.calendarValue());
						} else if (coreDatatype == CoreDatatype.XSD.DATETIMESTAMP) {
							return new CalendarMemLiteral(this, label, coreDatatype, literal.calendarValue());
						}
					}

					return new MemLiteral(this, label, datatype, coreDatatype);

				} catch (IllegalArgumentException e) {
					// Unable to parse literal label to primitive type
					return new MemLiteral(this, label, datatype);
				}
			}
		});
	}

	/**
	 * See {@link #getOrCreateMemValue(Value)} for description.
	 */
	public MemTriple getOrCreateMemTriple(Triple triple) {
		MemTriple memTriple = getMemTriple(triple);

		if (memTriple == null) {
			// Create a MemTriple and add it to the registry
			MemTriple newMemTriple = new MemTriple(this, getOrCreateMemResource(triple.getSubject()),
					getOrCreateMemURI(triple.getPredicate()), getOrCreateMemValue(triple.getObject()));
			boolean wasNew = tripleRegistry.add(newMemTriple);

			if (!wasNew) {
				return tripleRegistry.getOrAdd(triple, () -> newMemTriple);
			} else {
				return newMemTriple;
			}
		} else {
			return memTriple;
		}

	}

	@Override
	public IRI createIRI(String uri) {
		return getOrCreateMemURI(super.createIRI(uri));
	}

	@Override
	public IRI createIRI(String namespace, String localName) {
		return iriRegistry.getOrAdd(SimpleValueFactory.getInstance().createIRI(namespace, localName), () -> {

			if (namespace.indexOf(':') == -1) {
				throw new IllegalArgumentException("Not a valid (absolute) URI: " + namespace + localName);
			}

			String correctNamespace;
			String correctLocalName;

			if (!URIUtil.isCorrectURISplit(namespace, localName)) {
				IRI iri = super.createIRI(namespace + localName);
				correctNamespace = iri.getNamespace();
				correctLocalName = iri.getLocalName();

			} else {
				correctNamespace = namespace;
				correctLocalName = localName;
			}

			String sharedNamespace = namespaceRegistry.getOrAdd(correctNamespace, () -> correctNamespace);

			// Create a MemURI and add it to the registry
			return new MemIRI(this, sharedNamespace, correctLocalName);

		});

	}

	@Override
	public BNode createBNode(String nodeID) {
		return getOrCreateMemBNode(super.createBNode(nodeID));
	}

	@Override
	public Literal createLiteral(String value) {
		return getOrCreateMemLiteral(super.createLiteral(value));
	}

	@Override
	public Literal createLiteral(String value, String language) {
		return getOrCreateMemLiteral(super.createLiteral(value, language));
	}

	@Override
	public Literal createLiteral(String value, IRI datatype) {
		return getOrCreateMemLiteral(super.createLiteral(value, datatype));
	}

	@Override
	public Literal createLiteral(String value, CoreDatatype datatype) {
		return getOrCreateMemLiteral(super.createLiteral(value, datatype));
	}

	@Override
	public Literal createLiteral(boolean value) {
		MemLiteral newLiteral = new BooleanMemLiteral(this, value);
		return getSharedLiteral(newLiteral);
	}

	@Override
	public Literal createLiteral(XMLGregorianCalendar calendar) {
		MemLiteral newLiteral = new CalendarMemLiteral(this, calendar);
		return getSharedLiteral(newLiteral);
	}

	private Literal getSharedLiteral(MemLiteral newLiteral) {
		return literalRegistry.getOrAdd(newLiteral, () -> newLiteral);
	}

	@Override
	public Triple createTriple(Resource subject, IRI predicate, Value object) {
		return getOrCreateMemTriple(super.createTriple(subject, predicate, object));
	}
}