ValueSerializer.java

/*******************************************************************************
 * Copyright (c) 2023 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.collection.factory.mapdb;

import java.io.IOException;
import java.util.Optional;

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.ValueFactory;
import org.eclipse.rdf4j.model.base.CoreDatatype;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.mapdb.DataInput2;
import org.mapdb.DataOutput2;
import org.mapdb.Serializer;
import org.mapdb.serializer.SerializerIntegerPacked;
import org.mapdb.serializer.SerializerString;

/**
 * A minimally optimized serializer for values.
 *
 * @author Jerven Bolleman
 */
class ValueSerializer implements Serializer<Value> {
	private static final int IS_NEW_COREDATATYPE = 5;
	private static final int IS_RDF_DATATYPE = 4;
	private static final int IS_GEO_DATATYPE = 3;
	private static final int IS_XSD_DATATYPE = 2;
	private static final int NOT_COREDATYPE = 1;
	private static final int IS_LANGUAGE = 0;
	private static final int IS_BNODE = 0;
	private static final int IS_IRI = 1;
	private static final int IS_LITERAL = 2;
	private static final int IS_TRIPLE = 3;
	private static final int IS_NULL = 4;
	private final SerializerIntegerPacked si = new SerializerIntegerPacked();
	private final SerializerString ss = new SerializerString();
	private final ValueFactory vf;

	public ValueSerializer() {
		this(SimpleValueFactory.getInstance());
	}

	public ValueSerializer(ValueFactory vf) {
		super();
		this.vf = vf;
	}

	@Override
	public void serialize(DataOutput2 out, Value value) throws IOException {
		if (value instanceof BNode) {
			si.serialize(out, IS_BNODE);
			serializeBNode(out, (BNode) value);
		} else if (value instanceof IRI) {
			si.serialize(out, IS_IRI);
			serializeIRI(out, (IRI) value);
		} else if (value instanceof Literal) {
			si.serialize(out, IS_LITERAL);
			serializeLiteral(out, (Literal) value);
		} else if (value instanceof Triple) {
			si.serialize(out, IS_TRIPLE);
			serializeTriple(out, (Triple) value);
		} else {
			si.serialize(out, IS_NULL);
		}
	}

	private void serializeBNode(DataOutput2 out, BNode bnode) throws IOException {
		ss.serialize(out, bnode.getID());
	}

	private void serializeLiteral(DataOutput2 out, Literal value) throws IOException {
		final Optional<String> language = value.getLanguage();
		if (language.isPresent()) {
			si.serialize(out, IS_LANGUAGE);
			ss.serialize(out, value.stringValue());
			ss.serialize(out, language.get());
		} else {
			final CoreDatatype cd = value.getCoreDatatype();
			if (cd == null) {
				si.serialize(out, NOT_COREDATYPE);
				serializeIRI(out, value.getDatatype());
			} else if (cd.isXSDDatatype()) {
				si.serialize(out, IS_XSD_DATATYPE);
				si.serialize(out, ((CoreDatatype.XSD) cd).ordinal());
				// TODO optimize the storage of valid pure int etc.
				// without needing to parse strings
			} else if (cd.isGEODatatype()) {
				si.serialize(out, IS_GEO_DATATYPE);
				si.serialize(out, ((CoreDatatype.GEO) cd).ordinal());
			} else if (cd.isRDFDatatype()) {
				si.serialize(out, IS_RDF_DATATYPE);
				si.serialize(out, ((CoreDatatype.RDF) cd).ordinal());
			} else {
				si.serialize(out, IS_NEW_COREDATATYPE);
				serializeIRI(out, value.getDatatype());
			}
			ss.serialize(out, value.stringValue());
		}
	}

	private void serializeIRI(DataOutput2 out, IRI value) throws IOException {
		ss.serialize(out, value.stringValue());
	}

	private void serializeTriple(DataOutput2 out, Triple value) throws IOException {
		serialize(out, value.getSubject());
		serialize(out, value.getPredicate());
		serialize(out, value.getObject());
	}

	@Override
	public Value deserialize(DataInput2 input, int available) throws IOException {
		int t = si.deserialize(input, available);
		switch (t) {
		case IS_BNODE:
			return deserializeBnode(input, available);
		case IS_IRI:
			return deserializeIRI(input, available);
		case IS_LITERAL:
			return deserializeLiteral(input, available);
		case IS_TRIPLE:
			return deserializeTriple(input, available);
		case IS_NULL:
		default:
			return null;
		}
	}

	private Value deserializeTriple(DataInput2 input, int available) throws IOException {
		final Resource subj = (Resource) deserialize(input, available);
		final IRI pred = (IRI) deserialize(input, available);
		final Value obj = deserialize(input, available);
		return vf.createTriple(subj, pred, obj);
	}

	private Value deserializeLiteral(DataInput2 input, int available) throws IOException {
		int t = si.deserialize(input, available);
		switch (t) {
		case IS_LANGUAGE: {
			String language = ss.deserialize(input, available);
			String value = ss.deserialize(input, available);
			return vf.createLiteral(value, language);
		}
		case NOT_COREDATYPE: {
			IRI datatype = deserializeIRI(input, available);
			String value = ss.deserialize(input, available);
			return vf.createLiteral(value, datatype);
		}
		case IS_XSD_DATATYPE: {
			CoreDatatype.XSD datatype = CoreDatatype.XSD.values()[si.deserialize(input, available)];
			String value = ss.deserialize(input, available);
			return vf.createLiteral(value, datatype);
		}
		case IS_GEO_DATATYPE: {
			CoreDatatype.GEO datatype = CoreDatatype.GEO.values()[si.deserialize(input, available)];
			String value = ss.deserialize(input, available);
			return vf.createLiteral(value, datatype);
		}
		case IS_RDF_DATATYPE: {
			CoreDatatype.RDF datatype = CoreDatatype.RDF.values()[si.deserialize(input, available)];
			String value = ss.deserialize(input, available);
			return vf.createLiteral(value, datatype);
		}
		case IS_NEW_COREDATATYPE: {
			IRI datatype = deserializeIRI(input, available);
			String value = ss.deserialize(input, available);
			return vf.createLiteral(value, datatype);
		}
		}
		return null;
	}

	private IRI deserializeIRI(DataInput2 input, int available) throws IOException {
		return vf.createIRI(ss.deserialize(input, available));
	}

	private BNode deserializeBnode(DataInput2 input, int available) throws IOException {
		return vf.createBNode(ss.deserialize(input, available));
	}

}