ZeroLengthPathIteration.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.query.algebra.evaluation.iterator;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;

import org.eclipse.rdf4j.collection.factory.api.CollectionFactory;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.iteration.LookAheadIteration;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.MutableBindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.algebra.StatementPattern;
import org.eclipse.rdf4j.query.algebra.StatementPattern.Scope;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext;

public class ZeroLengthPathIteration extends LookAheadIteration<BindingSet> {

	private static final Literal OBJECT = SimpleValueFactory.getInstance().createLiteral("object");

	private static final Literal SUBJECT = SimpleValueFactory.getInstance().createLiteral("subject");

	public static final String ANON_SUBJECT_VAR = "zero_length_internal_start";

	public static final String ANON_PREDICATE_VAR = "zero_length_internal_pred";

	public static final String ANON_OBJECT_VAR = "zero_length_internal_end";

	public static final String ANON_SEQUENCE_VAR = "zero_length_internal_seq";

	private QueryBindingSet result;

	private final Value subj;

	private final Value obj;

	private final BindingSet bindings;

	private CloseableIteration<BindingSet> iter;

	private Set<Value> reportedValues;

	private final Var contextVar;

	private final EvaluationStrategy evaluationStrategy;

	private final QueryEvaluationStep precompile;

	private final QueryEvaluationContext context;

	private final BiConsumer<Value, MutableBindingSet> setSubject;

	private final BiConsumer<Value, MutableBindingSet> setObject;

	private final BiConsumer<Value, MutableBindingSet> setContext;

	private final CollectionFactory cf;

	public ZeroLengthPathIteration(EvaluationStrategy evaluationStrategyImpl, Var subjectVar, Var objVar, Value subj,
			Value obj, Var contextVar, BindingSet bindings, QueryEvaluationContext context) {
		this.evaluationStrategy = evaluationStrategyImpl;
		this.context = context;
		this.result = new QueryBindingSet(bindings);
		this.contextVar = contextVar;
		this.subj = subj;
		this.obj = obj;
		this.bindings = bindings;
		Var startVar = createAnonVar(ANON_SUBJECT_VAR);
		Var predicate = createAnonVar(ANON_PREDICATE_VAR);
		Var endVar = createAnonVar(ANON_OBJECT_VAR);

		StatementPattern subjects;
		if (contextVar != null) {
			subjects = new StatementPattern(Scope.NAMED_CONTEXTS, startVar, predicate, endVar, contextVar.clone());
		} else {
			subjects = new StatementPattern(startVar, predicate, endVar);
		}

		precompile = evaluationStrategy.precompile(subjects, context);
		setSubject = context.addBinding(subjectVar.getName());
		setObject = context.addBinding(objVar.getName());
		if (contextVar != null) {
			setContext = context.addBinding(contextVar.getName());
		} else {
			setContext = null;
		}
		this.cf = evaluationStrategy.getCollectionFactory().get();
	}

	@Override
	protected BindingSet getNextElement() throws QueryEvaluationException {
		if (subj == null && obj == null) {
			if (this.reportedValues == null) {
				reportedValues = cf.createValueSet();
			}
			if (this.iter == null) {
				// join with a sequence so we iterate over every entry twice
				QueryBindingSet bs1 = new QueryBindingSet(1);
				bs1.addBinding(ANON_SEQUENCE_VAR, SUBJECT);
				QueryBindingSet bs2 = new QueryBindingSet(1);
				bs2.addBinding(ANON_SEQUENCE_VAR, OBJECT);
				List<BindingSet> seqList = Arrays.<BindingSet>asList(bs1, bs2);
				iter = new CrossProductIteration(createIteration(), seqList);
			}

			while (iter.hasNext()) {
				BindingSet bs = iter.next();

				boolean isSubjOrObj = bs.getValue(ANON_SEQUENCE_VAR).stringValue().equals("subject");
				String endpointVarName = isSubjOrObj ? ANON_SUBJECT_VAR : ANON_OBJECT_VAR;
				Value v = bs.getValue(endpointVarName);

				if (reportedValues.add(v)) {
					MutableBindingSet next = context.createBindingSet(bindings);
					setSubject.accept(v, next);
					setObject.accept(v, next);
					if (setContext != null) {
						Value context = bs.getValue(contextVar.getName());
						if (context != null) {
							setContext.accept(context, next);
						}
					}
					return next;
				}
			}
			iter.close();

			// if we're done, throw away the cached list of values to avoid hogging
			// resources
			reportedValues = null;
			return null;
		} else {
			if (result != null) {
				if (obj == null && subj != null) {
					setObject.accept(subj, result);
				} else if (subj == null && obj != null) {
					setSubject.accept(obj, result);
				} else if (subj != null && subj.equals(obj)) {
					// empty bindings
					// (result but nothing to bind as subjectVar and objVar are both fixed)
				} else {
					result = null;
				}
			}

			QueryBindingSet next = result;
			result = null;
			return next;
		}
	}

	private CloseableIteration<BindingSet> createIteration() throws QueryEvaluationException {
		CloseableIteration<BindingSet> iter = precompile.evaluate(bindings);
		return iter;
	}

	public Var createAnonVar(String varName) {
		return new Var(varName, true);
	}

	@Override
	protected void handleClose() throws QueryEvaluationException {
		cf.close();
	}
}