ShaclAstLists.java

/*******************************************************************************
 * Copyright (c) 2020 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.shacl.ast;

import static org.eclipse.rdf4j.model.util.Values.bnode;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;

/**
 * Internal utility methods for the SHACL AST to quickly convert between java collections and RDF collections. These
 * utilities are not meant for general use as they make several simplifying assumptions for performance reasons:
 *
 * <ul>
 * <li>they add type coercion
 * <Li>they rely on the use of "stable" identifiers for blank nodes, so that duplicates can be quickly detected and
 * discarded
 * </ul>
 *
 * @apiNote This feature is for internal use only: its existence, signature or behavior may change without warning from
 *          one release to the next.
 */
@InternalUseOnly
public class ShaclAstLists {

	private static final String uuid = UUID.randomUUID().toString();

	public static void listToRdf(Collection<? extends Value> values, Resource head, Model model) {
		// The Turtle parser does not add "a rdf:List" statements when parsing the shorthand list format,
		// so we don't add rdf:List when writing it out either.

		int counter = 0;
		Resource originalHead = head;

		Iterator<? extends Value> iter = values.iterator();
		while (iter.hasNext()) {
			Value value = iter.next();
			model.add(head, RDF.FIRST, value);

			if (iter.hasNext()) {
				Resource next = bnode(originalHead.stringValue() + uuid + counter++);
				model.add(head, RDF.REST, next);
				head = next;
			} else {
				model.add(head, RDF.REST, RDF.NIL);
			}
		}
	}

	private static List<Value> toList(ShapeSource shapeSource, Resource head) {
		List<Value> ret = new ArrayList<>();
		while (!RDF.NIL.equals(head)) {
			Value value = shapeSource.getRdfFirst(head);
			ret.add(value);
			head = shapeSource.getRdfRest(head);
		}

		return ret;

	}

	public static <T extends Value> List<T> toList(ShapeSource shapeSource, Resource head, Class<T> type) {
		if (type == Value.class) {
			return (List<T>) toList(shapeSource, head);
		}

		List<T> ret = new ArrayList<>();
		while (!RDF.NIL.equals(head)) {
			Value value = shapeSource.getRdfFirst(head);
			if (type.isInstance(value)) {
				ret.add(((T) value));
			} else {
				if (value == null) {
					throw new IllegalStateException("RDF list node " + head + " does not have a value for rdf:first");
				} else {
					throw new IllegalStateException("RDF list should contain only type " + type.getSimpleName()
							+ ", but found " + value.getClass().getSimpleName());
				}
			}

			head = shapeSource.getRdfRest(head);
		}

		return ret;

	}

}