FailureInjectingFileChannel.java

/*******************************************************************************
 * Copyright (c) 2025 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.nativerdf.testutil;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

/**
 * Delegating FileChannel that can simulate failures for testing.
 */
public class FailureInjectingFileChannel extends FileChannel {

	private final FileChannel delegate;

	// simple toggles for simulation
	private volatile boolean failNextWrite;
	private volatile boolean failNextForce;

	public FailureInjectingFileChannel(FileChannel delegate) {
		this.delegate = delegate;
	}

	public void setFailNextWrite(boolean fail) {
		this.failNextWrite = fail;
	}

	public void setFailNextForce(boolean fail) {
		this.failNextForce = fail;
	}

	@Override
	public int read(ByteBuffer dst) throws IOException {
		return delegate.read(dst);
	}

	@Override
	public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
		return delegate.read(dsts, offset, length);
	}

	@Override
	public int write(ByteBuffer src) throws IOException {
		if (failNextWrite) {
			failNextWrite = false;
			throw new IOException("Simulated write failure");
		}
		return delegate.write(src);
	}

	@Override
	public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
		if (failNextWrite) {
			failNextWrite = false;
			throw new IOException("Simulated write failure");
		}
		return delegate.write(srcs, offset, length);
	}

	@Override
	public long position() throws IOException {
		return delegate.position();
	}

	@Override
	public FileChannel position(long newPosition) throws IOException {
		delegate.position(newPosition);
		return this;
	}

	@Override
	public long size() throws IOException {
		return delegate.size();
	}

	@Override
	public FileChannel truncate(long size) throws IOException {
		delegate.truncate(size);
		return this;
	}

	@Override
	public void force(boolean metaData) throws IOException {
		if (failNextForce) {
			failNextForce = false;
			throw new IOException("Simulated force failure");
		}
		delegate.force(metaData);
	}

	@Override
	public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
		return delegate.transferTo(position, count, target);
	}

	@Override
	public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
		return delegate.transferFrom(src, position, count);
	}

	@Override
	public int read(ByteBuffer dst, long position) throws IOException {
		return delegate.read(dst, position);
	}

	@Override
	public int write(ByteBuffer src, long position) throws IOException {
		if (failNextWrite) {
			failNextWrite = false;
			throw new IOException("Simulated write failure");
		}
		return delegate.write(src, position);
	}

	@Override
	protected void implCloseChannel() throws IOException {
		delegate.close();
	}

	@Override
	public FileLock lock(long position, long size, boolean shared) throws IOException {
		return delegate.lock(position, size, shared);
	}

	@Override
	public FileLock tryLock(long position, long size, boolean shared) throws IOException {
		return delegate.tryLock(position, size, shared);
	}

	@Override
	public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
		return delegate.map(mode, position, size);
	}
}