SimpleBindingSet.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.shacl.ast.planNodes;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.impl.SimpleBinding;

/**
 * A simple binding set tuned for the use case that the ShaclSail has.
 */
public class SimpleBindingSet implements BindingSet {

	private static final long serialVersionUID = 1001660194269450975L;

	private final Set<String> bindingNamesSet;
	private final Binding[] bindings;

	private int cachedHashCode = 0;

	public SimpleBindingSet(Set<String> bindingNamesSet, List<String> varNamesList, List<Value> values) {
		assert varNamesList.size() == values.size();
		assert !varNamesList.isEmpty();
		this.bindingNamesSet = bindingNamesSet;
		this.bindings = new Binding[varNamesList.size()];

		for (int i = 0; i < varNamesList.size(); i++) {
			bindings[i] = new SimpleBinding(varNamesList.get(i), values.get(i));
		}

	}

	public SimpleBindingSet(Set<String> bindingNamesSet, Binding[] bindings) {
		assert bindings.length > 0;
		this.bindingNamesSet = bindingNamesSet;
		this.bindings = bindings;
		assert Arrays.stream(bindings).noneMatch(Objects::isNull);
	}

	@Override
	public Iterator<Binding> iterator() {
		return Arrays.asList(bindings).iterator();
	}

	@Override
	public Set<String> getBindingNames() {
		return bindingNamesSet;
	}

	@Override
	public Binding getBinding(String bindingName) {
		for (Binding binding : bindings) {
			if (binding.getName().equals(bindingName)) {
				return binding;
			}
		}
		return null;
	}

	@Override
	public boolean hasBinding(String bindingName) {
		return bindingNamesSet.contains(bindingName);
	}

	@Override
	public Value getValue(String bindingName) {
		Binding binding = getBinding(bindingName);
		if (binding != null) {
			return binding.getValue();
		}
		return null;
	}

	@Override
	public int size() {
		return bindings.length;
	}

	@Override
	public int hashCode() {
		if (cachedHashCode == 0) {
			int hashCode = 0;

			for (Binding binding : bindings) {
				hashCode ^= binding.getName().hashCode() ^ binding.getValue().hashCode();
			}
			cachedHashCode = hashCode;
		}
		return cachedHashCode;
	}

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

		if (!(other instanceof BindingSet)) {
			return false;
		}

		BindingSet that = (BindingSet) other;

		if (this.size() != that.size()) {
			return false;
		}

		if (this.size() == 1) {
			Binding binding = iterator().next();
			Binding thatBinding = that.iterator().next();

			return binding.getName().equals(thatBinding.getName()) && binding.getValue().equals(thatBinding.getValue());
		}

		// Compare other's bindings to own
		for (Binding binding : that) {
			Value ownValue = getValue(binding.getName());

			if (!binding.getValue().equals(ownValue)) {
				// Unequal bindings for this name
				return false;
			}
		}

		return true;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(32 * size());

		sb.append('[');

		Iterator<Binding> iter = iterator();
		while (iter.hasNext()) {
			sb.append(iter.next().toString());
			if (iter.hasNext()) {
				sb.append(';');
			}
		}

		sb.append(']');

		return sb.toString();
	}

	@Override
	public boolean isEmpty() {
		return false;
	}
}