package org.jruby.util.io;
import jnr.enxio.channels.NativeDeviceChannel;
import jnr.enxio.channels.NativeSelectableChannel;
import jnr.posix.FileStat;
import jnr.posix.POSIX;
import org.jruby.platform.Platform;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.atomic.AtomicInteger;
public class ChannelFD implements Closeable {
public ChannelFD(Channel fd, POSIX posix, FilenoUtil filenoUtil, int flags) {
assert fd != null;
this.ch = fd;
this.posix = posix;
this.filenoUtil = filenoUtil;
this.openflags = flags;
initFileno(false);
initChannelTypes();
refs = new AtomicInteger(1);
filenoUtil.registerWrapper(realFileno, this);
filenoUtil.registerWrapper(fakeFileno, this);
}
public ChannelFD(Channel fd, POSIX posix, FilenoUtil filenoUtil) {
this(fd, posix, filenoUtil, -1);
}
private void initFileno(boolean allocate) {
realFileno = FilenoUtil.filenoFrom(ch);
if (Platform.IS_WINDOWS && realFileno == -1 && openflags >= 0) {
if (allocate) {
realFileno = filenoUtil.filenoFromHandleIn(ch, openflags);
needsClosing = realFileno != -1;
maybeHandle = false;
} else {
maybeHandle = true;
}
}
if (realFileno == -1) {
fakeFileno = filenoUtil.getNewFileno();
} else {
fakeFileno = -1;
}
}
public ChannelFD dup() {
if (realFileno != -1 && !Platform.IS_WINDOWS) {
return new ChannelFD(new NativeDeviceChannel(posix.dup(realFileno)), posix, filenoUtil);
}
Channel ch = this.ch;
ChannelFD fd = new ChannelFD(ch, posix, filenoUtil);
fd.refs = refs;
fd.refs.incrementAndGet();
return fd;
}
public int dup2From(POSIX posix, ChannelFD dup2Source) {
if (dup2Source.realFileno != -1 && realFileno != -1 && chFile == null) {
return posix.dup2(dup2Source.realFileno, realFileno);
}
this.ch = dup2Source.ch;
initFileno(false);
initChannelTypes();
this.refs = dup2Source.refs;
this.refs.incrementAndGet();
this.currentLock = dup2Source.currentLock;
return fakeFileno;
}
public void close() throws IOException {
finish();
}
public int bestFileno() {
return realFileno == -1 ? fakeFileno : realFileno;
}
public int bestFileno(boolean forceFileno) {
if (maybeHandle && forceFileno) {
initFileno(true);
assert maybeHandle == false : "lazy handle creation state changed";
filenoUtil.registerWrapper(realFileno, this);
filenoUtil.registerWrapper(fakeFileno, this);
}
return bestFileno();
}
private void finish() throws IOException {
synchronized (refs) {
if (refs.get() <= 0) {
throw new ClosedChannelException();
}
if (!ch.isOpen()) {
throw new ClosedChannelException();
}
int count = refs.decrementAndGet();
if (count <= 0) {
try {
ch.close();
if (needsClosing) {
filenoUtil.closeFilenoHandle(realFileno);
}
} finally {
filenoUtil.unregisterWrapper(realFileno);
filenoUtil.unregisterWrapper(fakeFileno);
}
}
}
}
private void initChannelTypes() {
assert realFileno != -1 || fakeFileno != -1 : "initialize filenos before initChannelTypes";
if (ch instanceof ReadableByteChannel) chRead = (ReadableByteChannel)ch;
else chRead = null;
if (ch instanceof WritableByteChannel) chWrite = (WritableByteChannel)ch;
else chWrite = null;
if (ch instanceof SeekableByteChannel) chSeek = (SeekableByteChannel)ch;
else chSeek = null;
if (ch instanceof SelectableChannel) chSelect = (SelectableChannel)ch;
else chSelect = null;
if (ch instanceof FileChannel) chFile = (FileChannel)ch;
else chFile = null;
if (ch instanceof SocketChannel) chSock = (SocketChannel)ch;
else chSock = null;
if (ch instanceof NativeSelectableChannel) chNative = (NativeSelectableChannel)ch;
else chNative = null;
if (chNative != null) {
FileStat stat = posix.fstat(chNative.getFD());
if (stat.isFile()) {
chSelect = null;
isNativeFile = true;
}
}
}
public Channel ch;
public ReadableByteChannel chRead;
public WritableByteChannel chWrite;
public SeekableByteChannel chSeek;
public SelectableChannel chSelect;
public FileChannel chFile;
public SocketChannel chSock;
public NativeSelectableChannel chNative;
public int realFileno;
public int fakeFileno;
private AtomicInteger refs;
public ThreadLocal<FileLock> currentLock = new ThreadLocal<>();
private final POSIX posix;
public boolean isNativeFile = false;
private final FilenoUtil filenoUtil;
private boolean needsClosing = false;
private boolean maybeHandle = false;
private final int openflags;
}