Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ji] make RubyIOs Java Closeable/Flushable + to_java to return stream #4758

Merged
merged 1 commit into from Aug 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyArgsFile.java
Expand Up @@ -506,7 +506,7 @@ public static IRubyObject skip(IRubyObject recv) {

public static void argf_close(ThreadContext context, IRubyObject file) {
if(file instanceof RubyIO) {
((RubyIO)file).rbIoClose(context.runtime);
((RubyIO) file).rbIoClose(context);
} else {
file.callMethod(context, "close");
}
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/RubyFile.java
Expand Up @@ -282,16 +282,16 @@ public RubyFile(Ruby runtime, String path, FileChannel channel) {
}

@Override
protected IRubyObject rbIoClose(Ruby runtime) {
protected IRubyObject rbIoClose(ThreadContext context) {
// Make sure any existing lock is released before we try and close the file
if (openFile.currentLock != null) {
try {
openFile.currentLock.release();
} catch (IOException e) {
throw getRuntime().newIOError(e.getMessage());
throw context.runtime.newIOError(e.getMessage());
}
}
return super.rbIoClose(runtime);
return super.rbIoClose(context);
}

@JRubyMethod(required = 1)
Expand Down
81 changes: 48 additions & 33 deletions core/src/main/java/org/jruby/RubyIO.java
Expand Up @@ -58,6 +58,8 @@
import org.jruby.util.io.PosixShim;
import jnr.constants.platform.Fcntl;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand Down Expand Up @@ -111,9 +113,8 @@
* @author jpetersen
*/
@JRubyClass(name="IO", include="Enumerable")
public class RubyIO extends RubyObject implements IOEncodable {
// We use a highly uncommon string to represent the paragraph delimiter (100% soln not worth it)
public static final ByteList PARAGRAPH_DELIMETER = ByteList.create("PARAGRPH_DELIM_MRK_ER");
public class RubyIO extends RubyObject implements IOEncodable, Closeable, Flushable {

public static final ByteList PARAGRAPH_SEPARATOR = ByteList.create("\n\n");
public static final String CLOSED_STREAM_MSG = "closed stream";

Expand Down Expand Up @@ -1111,7 +1112,7 @@ public void setEncoding(ThreadContext context, IRubyObject v1, IRubyObject v2, I
RubyString internalAsString = (RubyString) tmp;

// No encoding '-'
if (internalAsString.size() == 1 && internalAsString.asJavaString().equals("-")) {
if (isDash(internalAsString)) {
/* Special case - "-" => no transcoding */
holder.enc = holder.enc2;
holder.enc2 = null;
Expand Down Expand Up @@ -1174,11 +1175,10 @@ public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObj

public static IRubyObject ensureYieldClose(ThreadContext context, IRubyObject port, Block block) {
if (block.isGiven()) {
Ruby runtime = context.runtime;
try {
return block.yield(context, port);
} finally {
ioClose(runtime, port);
ioClose(context, port);
}
}
return port;
Expand Down Expand Up @@ -1995,20 +1995,19 @@ public boolean isClosed() {
* MRI: rb_io_close_m
*/
@JRubyMethod
public IRubyObject close() {
Ruby runtime = getRuntime();
if (isClosed()) {
return runtime.getNil();
}
return rbIoClose(runtime);
public IRubyObject close(final ThreadContext context) {
if (isClosed()) return context.nil;
return rbIoClose(context);
}

public final void close() { close(getRuntime().getCurrentContext()); }

// io_close
protected static IRubyObject ioClose(Ruby runtime, IRubyObject io) {
ThreadContext context = runtime.getCurrentContext();
protected static IRubyObject ioClose(ThreadContext context, IRubyObject io) {
IOSites sites = sites(context);
IRubyObject closed = io.checkCallMethod(context, sites.closed_checked);
if (closed != null && closed.isTrue()) return io;
final Ruby runtime = context.runtime;
IRubyObject oldExc = runtime.getGlobalVariables().get("$!"); // Save $!
try {
closed = io.checkCallMethod(context, sites.close_checked);
Expand All @@ -2025,8 +2024,7 @@ protected static IRubyObject ioClose(Ruby runtime, IRubyObject io) {
}

// rb_io_close
protected IRubyObject rbIoClose(Ruby runtime) {
ThreadContext context = runtime.getCurrentContext();
protected IRubyObject rbIoClose(ThreadContext context) {
OpenFile fptr;
RubyIO write_io;
OpenFile write_fptr;
Expand All @@ -2038,7 +2036,7 @@ protected IRubyObject rbIoClose(Ruby runtime) {
boolean locked = write_fptr.lock();
try {
if (write_fptr != null && write_fptr.fd() != null) {
write_fptr.cleanup(runtime, true);
write_fptr.cleanup(context.runtime, true);
}
} finally {
if (locked) write_fptr.unlock();
Expand All @@ -2049,8 +2047,9 @@ protected IRubyObject rbIoClose(Ruby runtime) {

boolean locked = fptr.lock();
try {
if (fptr == null) return runtime.getNil();
if (fptr.fd() == null) return runtime.getNil();
if (fptr == null) return context.nil;
if (fptr.fd() == null) return context.nil;
final Ruby runtime = context.runtime;

// interrupt waiting threads
fptr.interruptBlockingThreads(context);
Expand Down Expand Up @@ -2080,7 +2079,7 @@ protected IRubyObject rbIoClose(Ruby runtime) {
if (locked) fptr.unlock();
}

return runtime.getNil();
return context.nil;
}

// MRI: rb_io_close_write
Expand All @@ -2103,8 +2102,7 @@ public IRubyObject close_write(ThreadContext context) {
throw runtime.newErrnoFromErrno(Helpers.errnoFromException(ioe), fptr.getPath());
}
fptr.setMode(fptr.getMode() & ~OpenFile.WRITABLE);
if (!fptr.isReadable())
return write_io.rbIoClose(runtime);
if (!fptr.isReadable()) return write_io.rbIoClose(context);
return context.nil;
}

Expand All @@ -2127,7 +2125,7 @@ public IRubyObject close_write(ThreadContext context) {
}
}

write_io.rbIoClose(runtime);
write_io.rbIoClose(context);
return context.nil;
}

Expand All @@ -2149,8 +2147,7 @@ public IRubyObject close_read(ThreadContext context) {
throw runtime.newErrnoFromErrno(Helpers.errnoFromException(ioe), fptr.getPath());
}
fptr.setMode(fptr.getMode() & ~OpenFile.READABLE);
if (!fptr.isWritable())
return rbIoClose(runtime);
if (!fptr.isWritable()) return rbIoClose(context);
return context.nil;
}

Expand Down Expand Up @@ -2184,7 +2181,7 @@ public IRubyObject close_read(ThreadContext context) {
if (locked) fptr.unlock();
}

return rbIoClose(runtime);
return rbIoClose(context);
}

@JRubyMethod(name = "close_on_exec=", notImplemented = true)
Expand All @@ -2210,6 +2207,8 @@ public RubyIO flush(ThreadContext context) {
return flushRaw(context, true);
}

public void flush() { flush(getRuntime().getCurrentContext()); }

// rb_io_flush_raw
protected RubyIO flushRaw(ThreadContext context, boolean sync) {
OpenFile fptr;
Expand Down Expand Up @@ -3721,7 +3720,7 @@ public static IRubyObject ioStaticWrite(ThreadContext context, IRubyObject recv,
try {
return io.write(context, string, false);
}
finally { ioClose(runtime, io); }
finally { ioClose(context, io); }
}

static IRubyObject seekBeforeAccess(ThreadContext context, RubyIO io, IRubyObject offset, int mode) {
Expand Down Expand Up @@ -3925,7 +3924,7 @@ public static IRubyObject popen(ThreadContext context, IRubyObject recv, IRubyOb

RubyPOpen pOpen = new RubyPOpen(runtime, args);

if ("-".equals(pOpen.cmd.toString())) {
if (isDash(pOpen.cmd)) {
throw runtime.newNotImplementedError("popen(\"-\") is unimplemented");
}

Expand Down Expand Up @@ -4084,16 +4083,16 @@ public static IRubyObject ensureYieldClosePipes(ThreadContext context, IRubyObje
try {
return block.yield(context, obj);
} finally {
pipePairClose(context.runtime, r, w);
pipePairClose(context, r, w);
}
}

// MRI: pipe_pair_close
private static void pipePairClose(Ruby runtime, RubyIO r, RubyIO w) {
private static void pipePairClose(ThreadContext context, RubyIO r, RubyIO w) {
try {
ioClose(runtime, r);
ioClose(context, r);
} finally {
ioClose(runtime, w);
ioClose(context, w);
}
}

Expand Down Expand Up @@ -4677,6 +4676,18 @@ public boolean getBOM() {
return openFile.isBOM();
}

@Override
public Object toJava(Class target) {
if (target == java.io.InputStream.class) {
getOpenFile().checkReadable(getRuntime().getCurrentContext());
return getInStream();
}
if (target == java.io.OutputStream.class) {
getOpenFile().checkWritable(getRuntime().getCurrentContext());
return getOutStream();
}
return super.toJava(target);
}

// MRI: rb_io_ascii8bit_binmode
protected RubyIO setAscii8bitBinmode() {
Expand All @@ -4688,10 +4699,14 @@ protected RubyIO setAscii8bitBinmode() {
return this;
}

private static boolean isDash(RubyString str) {
return str.size() == 1 && str.getByteList().get(0) == '-'; // "-".equals(str.toString());
}

public final OpenFile MakeOpenFile() {
Ruby runtime = getRuntime();
if (openFile != null) {
rbIoClose(runtime);
rbIoClose(runtime.getCurrentContext());
rb_io_fptr_finalize(runtime, openFile);
openFile = null;
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyKernel.java
Expand Up @@ -1460,7 +1460,7 @@ public static IRubyObject backquote(ThreadContext context, IRubyObject recv, IRu

fptr = ((RubyIO)port).getOpenFileChecked();
result = fptr.readAll(context, fptr.remainSize(), context.nil);
((RubyIO)port).rbIoClose(runtime);
((RubyIO)port).rbIoClose(context);

return result;
}
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/java/org/jruby/ext/socket/RubyBasicSocket.java
Expand Up @@ -444,15 +444,14 @@ public IRubyObject close_read(ThreadContext context) {
}

private IRubyObject closeHalf(ThreadContext context, int closeHalf) {
Ruby runtime = context.runtime;
OpenFile fptr;

int otherHalf = closeHalf == OpenFile.READABLE ? OpenFile.WRITABLE : OpenFile.READABLE;

fptr = getOpenFileChecked();
if ((fptr.getMode() & otherHalf) == 0) {
// shutdown fully
return rbIoClose(runtime);
return rbIoClose(context);
}

// shutdown half
Expand Down
12 changes: 5 additions & 7 deletions core/src/main/java/org/jruby/ext/socket/RubySocket.java
Expand Up @@ -696,17 +696,15 @@ protected IRubyObject addrFor(ThreadContext context, InetSocketAddress addr, boo
return new Addrinfo(runtime, runtime.getClass("Addrinfo"), addr.getAddress(), addr.getPort(), Sock.SOCK_DGRAM);
}

@Override
@JRubyMethod
public IRubyObject close() {
public IRubyObject close(final ThreadContext context) {
if (getOpenFile() != null) {
Ruby runtime = getRuntime();
if (isClosed()) {
return runtime.getNil();
}
if (isClosed()) return context.nil;
openFile.checkClosed();
return rbIoClose(runtime);
return rbIoClose(context);
}
return getRuntime().getNil();
return context.nil;
}

@Override
Expand Down
16 changes: 8 additions & 8 deletions core/src/main/java/org/jruby/ext/tempfile/Tempfile.java
Expand Up @@ -169,8 +169,7 @@ public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block)

@JRubyMethod(visibility = PUBLIC)
public IRubyObject open(ThreadContext context) {
Ruby runtime = context.runtime;
if (!isClosed()) rbIoClose(runtime);
if (!isClosed()) rbIoClose(context);

// MRI doesn't do this, but we need to reset to blank slate
openFile = null;
Expand All @@ -182,7 +181,7 @@ public IRubyObject open(ThreadContext context) {

@JRubyMethod(visibility = PROTECTED)
public IRubyObject _close(ThreadContext context) {
return !isClosed() ? super.close() : context.nil;
return !isClosed() ? super.close(context) : context.nil;
}

@JRubyMethod(optional = 1, visibility = PUBLIC)
Expand Down Expand Up @@ -252,12 +251,13 @@ public IRubyObject size(ThreadContext context) {
}
}

public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
return open19(context, recv, args, block);
@Deprecated
public static IRubyObject open19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
return open(context, recv, args, block);
}

@JRubyMethod(required = 1, optional = 1, meta = true)
public static IRubyObject open19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
RubyClass klass = (RubyClass) recv;
Tempfile tempfile = (Tempfile) klass.newInstance(context, args, block);

Expand All @@ -277,10 +277,10 @@ public static IRubyObject open19(ThreadContext context, IRubyObject recv, IRubyO
public IRubyObject inspect() {
StringBuilder val = new StringBuilder();
val.append("#<Tempfile:").append(openFile.getPath());
if(!openFile.isOpen()) {
if (!openFile.isOpen()) {
val.append(" (closed)");
}
val.append(">");
val.append('>');
return getRuntime().newString(val.toString());
}

Expand Down
26 changes: 26 additions & 0 deletions spec/java_integration/addons/io_spec.rb
Expand Up @@ -57,6 +57,18 @@
expect(java.io.InputStream).to be === file.to_inputstream # old-naming
end

it "is coercible using to_java to java.io.InputStream" do
file = File.open(__FILE__)
first_ten = file.read(10)
file.seek(0)
stream = file.to_java java.io.InputStream
expect(java.io.InputStream).to be === stream

bytes = "0000000000".to_java_bytes
expect(stream.read(bytes)).to eq(10)
expect(String.from_java_bytes(bytes)).to eq(first_ten)
end

it "is coercible to java.io.OutputStream with IO#to_output_stream" do
file = Tempfile.new("io_spec")
stream = file.to_output_stream
Expand All @@ -72,6 +84,20 @@
expect(java.io.OutputStream).to be === file.to_outputstream # old-naming
end


it "is coercible using to_java to java.io.OutputStream" do
file = Tempfile.new("io_spec")
stream = file.to_java 'java.io.OutputStream'
expect(java.io.OutputStream).to be === stream

bytes = input_number.to_java_bytes
stream.write(bytes)
stream.flush
file.seek(0)
str = file.read(10)
expect(str).to eq(String.from_java_bytes(bytes))
end

it "gets an IO from a java.nio.channels.Channel" do
input = java.io.ByteArrayInputStream.new(input_number.to_java_bytes)
channel = java.nio.channels.Channels.newChannel(input)
Expand Down