Values.java
/*******************************************************************************
* Copyright (c) 2024 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.sparqlbuilder.constraint;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.sparqlbuilder.core.Variable;
import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern;
import org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf;
import org.eclipse.rdf4j.sparqlbuilder.rdf.RdfValue;
public class Values implements GraphPattern {
Variable[] variables;
RdfValue[][] solutionSequence;
private static final RdfValue UNDEF = new RdfValue() {
@Override
public String getQueryString() {
return "UNDEF";
}
};
public Values(Variable[] variables, RdfValue[][] solutionSequence) {
Objects.requireNonNull(solutionSequence);
Objects.requireNonNull(solutionSequence);
if (variables.length == 0) {
throw new IllegalArgumentException("no variables provided for VALUES clause");
}
if (solutionSequence.length == 0
|| solutionSequence[0] == null
|| solutionSequence[0].length == 0) {
throw new IllegalArgumentException("no values provided for VALUES clause");
}
if (solutionSequence[0].length != variables.length) {
throw new IllegalArgumentException(
solutionSequence[0].length
+ " values provided for "
+ variables.length
+ variables);
}
this.solutionSequence = solutionSequence;
this.variables = variables;
}
@Override
public String getQueryString() {
StringBuilder sb = new StringBuilder();
String parOpen = this.variables.length > 1 ? "( " : "";
String parClose = this.variables.length > 1 ? ") " : "";
sb.append("VALUES ").append(parOpen);
for (int i = 0; i < variables.length; i++) {
sb.append(variables[i].getQueryString()).append(" ");
}
sb.append(parClose).append("{").append(System.lineSeparator());
for (int i = 0; i < solutionSequence.length; i++) {
sb.append(" ").append(parOpen);
for (int j = 0; j < solutionSequence[i].length; j++) {
sb.append(solutionSequence[i][j].getQueryString()).append(" ");
}
sb.append(parClose).append(System.lineSeparator());
}
sb.append("}").append(System.lineSeparator());
return sb.toString();
}
public static VariablesBuilder builder() {
return new Builder();
}
public static class Builder implements VariablesBuilder, ValuesBuilder {
public Builder() {
}
private List<Variable> variables = new ArrayList<>();
private List<List<RdfValue>> values = new ArrayList<>();
private List<RdfValue> currentValues = new ArrayList<>();
@Override
public VariablesBuilder variables(Variable... variable) {
Arrays.stream(variable).forEach(this.variables::add);
return this;
}
/**
* Provide another value. This will fill up the current solution sequence. If this value is the last one (i.e.
* the solution sequence now is of the same length as the list of variables), the current solution sequence is
* recorded and a new solution sequence begins.
*
* @param value
* @return
*/
@Override
public ValuesBuilder value(RdfValue value) {
this.currentValues.add(valueOrUndef(value));
if (currentValues.size() >= variables.size()) {
this.values.add(currentValues);
currentValues = new ArrayList<>();
}
return this;
}
@Override
public ValuesBuilder values(RdfValue... values) {
if (this.variables.size() == 1) {
for (int i = 0; i < values.length; i++) {
this.values.add(List.of(valueOrUndef(values[i])));
}
} else if (this.variables.size() == values.length) {
this.values.add(Stream.of(values).map(Values::valueOrUndef).collect(Collectors.toList()));
} else {
throw new IllegalArgumentException(
"Provided list of values must match length of variables, or there must be only one variable.");
}
return this;
}
@Override
public ValuesBuilder values(Collection<RdfValue> values) {
return values(values.toArray(i -> new RdfValue[i]));
}
@Override
public ValuesBuilder iriValue(IRI value) {
return value(Rdf.iri(value));
}
@Override
public ValuesBuilder iriValues(IRI... values) {
return values(Stream.of(values).map(Rdf::iri).toArray(i -> new RdfValue[i]));
}
@Override
public ValuesBuilder iriValues(Collection<IRI> values) {
return iriValues(values.toArray(i -> new IRI[i]));
}
@Override
public Values build() {
if (this.values.isEmpty()) {
throw new IllegalArgumentException("No values provided");
}
if (!this.currentValues.isEmpty()) {
throw new IllegalArgumentException(
"Current solution sequence is not finished - you added too few or too many values.");
}
RdfValue[][] values = new RdfValue[this.values.size()][this.variables.size()];
for (int i = 0; i < this.values.size(); i++) {
List<RdfValue> current = this.values.get(i);
if (current.size() != this.variables.size()) {
throw new IllegalArgumentException(
String.format(
"You provided $d values for $d variables",
current.size(),
this.variables.size()));
}
for (int j = 0; j < current.size(); j++) {
values[i][j] = current.get(j);
}
}
return new Values(this.variables.toArray(size -> new Variable[size]), values);
}
}
public interface VariablesBuilder {
public VariablesBuilder variables(Variable... variable);
public ValuesBuilder value(RdfValue value);
public ValuesBuilder values(RdfValue... values);
public ValuesBuilder values(Collection<RdfValue> values);
public ValuesBuilder iriValue(IRI value);
public ValuesBuilder iriValues(IRI... values);
public ValuesBuilder iriValues(Collection<IRI> values);
}
public interface ValuesBuilder {
public ValuesBuilder value(RdfValue value);
public ValuesBuilder values(RdfValue... values);
public ValuesBuilder values(Collection<RdfValue> values);
public ValuesBuilder iriValue(IRI value);
public ValuesBuilder iriValues(IRI... values);
public ValuesBuilder iriValues(Collection<IRI> values);
public Values build();
}
private static RdfValue valueOrUndef(RdfValue value) {
if (value == null) {
return UNDEF;
}
return value;
}
}