LazyValidationReport.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.results.lazy;

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

import java.io.StringWriter;
import java.util.HashSet;
import java.util.List;

import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
import org.eclipse.rdf4j.model.impl.DynamicModel;
import org.eclipse.rdf4j.model.impl.DynamicModelFactory;
import org.eclipse.rdf4j.model.vocabulary.DASH;
import org.eclipse.rdf4j.model.vocabulary.OWL;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.model.vocabulary.RSX;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.model.vocabulary.XSD;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.WriterConfig;
import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
import org.eclipse.rdf4j.sail.shacl.results.ValidationReport;
import org.eclipse.rdf4j.sail.shacl.results.ValidationResult;

/**
 * A ValidationReport that will defer calculating any ValidationResults until the user asks for them
 */
@InternalUseOnly
public class LazyValidationReport extends ValidationReport {

	private List<ValidationResultIterator> validationResultIterators;
	private final long limit;

	public LazyValidationReport(List<ValidationResultIterator> validationResultIterators, long limit) {

		this.validationResultIterators = validationResultIterators;
		this.limit = limit;

	}

	private void evaluateLazyAspect() {
		if (validationResultIterators != null) {
			long counter = 0;
			for (ValidationResultIterator validationResultIterator : validationResultIterators) {
				while (validationResultIterator.hasNext()) {
					if (limit >= 0 && counter >= limit) {
						truncated = true;
						break;
					}
					counter++;

					validationResult.add(validationResultIterator.next());
				}

				this.conforms = conforms && validationResultIterator.conforms();
				this.truncated = truncated || validationResultIterator.isTruncated();
			}

			validationResultIterators = null;

		}

	}

	public Model asModel(Model model) {
		evaluateLazyAspect();

		model.add(getId(), SHACL.CONFORMS, literal(conforms));
		model.add(getId(), RDF.TYPE, SHACL.VALIDATION_REPORT);
		model.add(getId(), RDF4J.TRUNCATED, BooleanLiteral.valueOf(truncated));

		HashSet<Resource> rdfListDedupe = new HashSet<>();

		for (ValidationResult result : validationResult) {
			model.add(getId(), SHACL.RESULT, result.getId());
			result.asModel(model, rdfListDedupe);
		}

		return model;
	}

	public Model asModel() {
		DynamicModel emptyModel = new DynamicModelFactory().createEmptyModel();
		emptyModel.setNamespace(SHACL.NS);
		emptyModel.setNamespace(RSX.NS);
		emptyModel.setNamespace(DASH.NS);

		emptyModel.setNamespace(RDF.NS);
		emptyModel.setNamespace(RDFS.NS);
		emptyModel.setNamespace(OWL.NS);
		emptyModel.setNamespace(XSD.NS);

		emptyModel.setNamespace(RDF4J.NS);

		return asModel(emptyModel);
	}

	/**
	 * @return false if the changes violated a SHACL Shape
	 */
	public boolean conforms() {
		evaluateLazyAspect();
		return conforms;
	}

	/**
	 * @return list of ValidationResult with more information about each violation
	 */
	public List<ValidationResult> getValidationResult() {
		evaluateLazyAspect();
		return validationResult;
	}

	@Override
	public String toString() {
		StringWriter stringWriter = new StringWriter();

		WriterConfig writerConfig = new WriterConfig()
				.set(BasicWriterSettings.PRETTY_PRINT, true)
				.set(BasicWriterSettings.INLINE_BLANK_NODES, true);

		Rio.write(asModel(), stringWriter, RDFFormat.TURTLE, writerConfig);

		return stringWriter.toString()
				.replaceAll("(?m)^(@prefix)(.*)(\\.)$", "") // remove all lines that are prefix declarations
				.trim();
	}

	public boolean isTruncated() {
		evaluateLazyAspect();
		return truncated;
	}

}