BufferedSplitter.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.planNodes;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.sail.SailException;
import org.eclipse.rdf4j.sail.shacl.ast.ShaclUnsupportedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author H��vard Ottestad
* <p>
* Allows the iterator of one planNode to be used by multiple other nodes by buffering all results from the
* parent iterator. This will potentially take a fair bit of memory, but maybe be useful for perfomance so that
* we don't query the underlying datastores for the same data multiple times.
*/
public class BufferedSplitter implements PlanNodeProvider {
private static final AtomicLong idCounter = new AtomicLong();
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PlanNode parent;
private final boolean cached;
private volatile List<ValidationTuple> tuplesBuffer;
private long id = -1;
private boolean printed;
private BufferedSplitter(PlanNode parent, boolean cached) {
this.parent = parent;
this.cached = cached;
id = idCounter.incrementAndGet();
}
private BufferedSplitter(PlanNode parent) {
this(parent, true);
}
public static BufferedSplitter getInstance(PlanNode parent) {
if (parent instanceof BufferedSplitterPlaneNode
&& ((BufferedSplitterPlaneNode) parent).bufferedSplitter.cached == true) {
return ((BufferedSplitterPlaneNode) parent).bufferedSplitter;
}
return new BufferedSplitter(parent);
}
public static BufferedSplitter getInstance(PlanNode parent, boolean cached) {
if (parent instanceof BufferedSplitterPlaneNode
&& ((BufferedSplitterPlaneNode) parent).bufferedSplitter.cached == cached) {
return ((BufferedSplitterPlaneNode) parent).bufferedSplitter;
}
return new BufferedSplitter(parent, cached);
}
private synchronized void init() {
if (tuplesBuffer == null) {
tuplesBuffer = new ArrayList<>();
try (CloseableIteration<? extends ValidationTuple> iterator = parent.iterator()) {
while (iterator.hasNext()) {
ValidationTuple next = iterator.next();
tuplesBuffer.add(next);
}
}
}
}
public String getId() {
int length = (idCounter.get() + "").length();
return String.format("%0" + length + "d", id);
}
@Override
public PlanNode getPlanNode() {
return new BufferedSplitterPlaneNode(this, cached);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BufferedSplitter that = (BufferedSplitter) o;
return parent.equals(that.parent);
}
@Override
public int hashCode() {
return Objects.hash(parent);
}
public static class BufferedSplitterPlaneNode implements PlanNode {
private final BufferedSplitter bufferedSplitter;
public final boolean cached;
private boolean printed = false;
private ValidationExecutionLogger validationExecutionLogger;
public BufferedSplitterPlaneNode(BufferedSplitter bufferedSplitter, boolean cached) {
this.bufferedSplitter = bufferedSplitter;
this.cached = cached;
}
@Override
public CloseableIteration<? extends ValidationTuple> iterator() {
return new CloseableIteration<>() {
Iterator<ValidationTuple> iterator;
private void init() {
if (iterator == null) {
bufferedSplitter.init();
iterator = bufferedSplitter.tuplesBuffer.iterator();
}
}
@Override
public void close() throws SailException {
}
@Override
public boolean hasNext() throws SailException {
init();
return iterator.hasNext();
}
@Override
public ValidationTuple next() throws SailException {
init();
ValidationTuple tuple = iterator.next();
if (validationExecutionLogger.isEnabled()) {
validationExecutionLogger.log(depth(),
bufferedSplitter.parent.getClass().getSimpleName() + ":BufferedSplitter.next()", tuple,
bufferedSplitter.parent, getId(), null);
}
return tuple;
}
@Override
public void remove() throws SailException {
throw new ShaclUnsupportedException();
}
};
}
@Override
public int depth() {
return bufferedSplitter.parent.depth() + 1;
}
@Override
public void getPlanAsGraphvizDot(StringBuilder stringBuilder) {
if (bufferedSplitter.printed) {
return;
}
bufferedSplitter.printed = true;
stringBuilder.append(getId() + " [label=\"" + StringEscapeUtils.escapeJava(this.toString()) + "\"];")
.append("\n");
stringBuilder.append(bufferedSplitter.parent.getId() + " -> " + getId()).append("\n");
bufferedSplitter.parent.getPlanAsGraphvizDot(stringBuilder);
}
@Override
public String getId() {
return System.identityHashCode(bufferedSplitter) + "";
}
@Override
public String toString() {
return "BufferedSplitter" + (cached ? " (cached)" : "");
}
@Override
public void receiveLogger(ValidationExecutionLogger validationExecutionLogger) {
this.validationExecutionLogger = validationExecutionLogger;
bufferedSplitter.parent.receiveLogger(validationExecutionLogger);
}
@Override
public boolean producesSorted() {
return bufferedSplitter.parent.producesSorted();
}
@Override
public boolean requiresSorted() {
return bufferedSplitter.parent.requiresSorted();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BufferedSplitterPlaneNode that = (BufferedSplitterPlaneNode) o;
return bufferedSplitter.equals(that.bufferedSplitter);
}
@Override
public int hashCode() {
return Objects.hash(bufferedSplitter);
}
}
}