LmdbIRI.java

/*******************************************************************************
 * Copyright (c) 2021 Eclipse RDF4J contributors.
 *
 * 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.lmdb.model;

import java.io.ObjectStreamException;
import java.util.Objects;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.util.URIUtil;
import org.eclipse.rdf4j.sail.lmdb.ValueStoreRevision;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LmdbIRI implements LmdbResource, IRI {

	private static final long serialVersionUID = -5888138591826143179L;
	private static final Logger log = LoggerFactory.getLogger(LmdbIRI.class);

	/*-----------*
	 * Constants *
	 *-----------*/

	private volatile ValueStoreRevision revision;

	private volatile long internalID;

	private volatile boolean initialized = false;
	/**
	 * The IRI string.
	 */
	private String iriString;

	/**
	 * An index indicating the first character of the local name in the IRI string, -1 if not yet set.
	 */
	private int localNameIdx;

	/*--------------*
	 * Constructors *
	 *--------------*/

	public LmdbIRI(ValueStoreRevision revision, long internalID) {
		setInternalID(internalID, revision);
	}

	public LmdbIRI(ValueStoreRevision revision, String uri) {
		this(revision, uri, UNKNOWN_ID);
	}

	public LmdbIRI(ValueStoreRevision revision, String uri, long internalID) {
		setIRIString(uri);
		setInternalID(internalID, revision);
		this.initialized = true;
	}

	public LmdbIRI(ValueStoreRevision revision, String namespace, String localName) {
		this(revision, namespace, localName, UNKNOWN_ID);
	}

	public LmdbIRI(ValueStoreRevision revision, String namespace, String localName, long internalID) {
		this.revision = revision;
		setNamespaceAndIri(namespace, localName);
		setInternalID(internalID, revision);
	}

	/*---------*
	 * Methods *
	 *---------*/

	@Override
	public void setInternalID(long internalID, ValueStoreRevision revision) {
		this.internalID = internalID;
		this.revision = revision;
	}

	@Override
	public ValueStoreRevision getValueStoreRevision() {
		return revision;
	}

	@Override
	public long getInternalID() {
		return internalID;
	}

	private void setIRIString(String iriString) {
		Objects.requireNonNull(iriString, "iriString must not be null");

		if (iriString.indexOf(':') < 0) {
			throw new IllegalArgumentException("Not a valid (absolute) IRI: " + iriString);
		}

		this.iriString = iriString;
		this.localNameIdx = -1;
	}

	@Override
	public String getNamespace() {

		init();
		if (localNameIdx < 0) {
			localNameIdx = URIUtil.getLocalNameIndex(iriString);
		}

		return iriString.substring(0, localNameIdx);
	}

	@Override
	public String getLocalName() {

		init();
		if (localNameIdx < 0) {
			localNameIdx = URIUtil.getLocalNameIndex(iriString);
		}

		return iriString.substring(localNameIdx);
	}

	@Override
	public String stringValue() {
		if (iriString != null) {
			return iriString;
		}
		init();
		return iriString;
	}

	public void init() {
		if (iriString == null && !initialized) {
			synchronized (this) {
				if (!initialized) {
					boolean resolved = revision.resolveValue(internalID, this);
					if (!resolved) {
						log.warn("Could not resolve value");
					}
					initialized = resolved;
				}
			}
		}
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}

		if (o == null) {
			return false;
		}

		if (o.getClass() == LmdbIRI.class) {
			if (internalID == UNKNOWN_ID) {
				boolean equals = stringValue().equals(((IRI) o).stringValue());
				if (equals && revision.equals(((LmdbIRI) o).revision)) {
					internalID = ((LmdbIRI) o).internalID;
				}
				return equals;
			}

			LmdbIRI otherLmdbURI = (LmdbIRI) o;

			if (revision.equals(otherLmdbURI.revision)) {
				if (otherLmdbURI.internalID == UNKNOWN_ID) {
					boolean equals = stringValue().equals(((IRI) o).stringValue());
					if (equals) {
						otherLmdbURI.internalID = this.internalID;
					}
					return equals;
				}

				// LmdbURI's from the same revision of the same lmdb store, with
				// both ID's set
				boolean equal = internalID == otherLmdbURI.internalID;
				if (equal) {
					if (iriString == null) {
						iriString = otherLmdbURI.iriString;
						localNameIdx = otherLmdbURI.localNameIdx;
					} else if (otherLmdbURI.iriString == null) {
						otherLmdbURI.iriString = iriString;
						otherLmdbURI.localNameIdx = localNameIdx;
					}
				}
				return equal;
			}
		}

		if (!(o instanceof IRI)) {
			return false;
		}

		return stringValue().equals(((IRI) o).stringValue());
	}

	@Override
	public int hashCode() {
		if (this.iriString != null) {
			return this.iriString.hashCode();
		}

		init();
		return iriString.hashCode();
	}

	protected Object writeReplace() throws ObjectStreamException {
		init();
		return this;
	}

	@Override
	public String toString() {
		return stringValue();
	}

	public void setNamespaceAndIri(String namespace, String localName) {
		localNameIdx = namespace.length();
		this.iriString = namespace + localName;
	}
}