Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ji] make RubyIOs Java Closeable/Flushable + to_java to return stream
Browse files Browse the repository at this point in the history
... also some minor improvements -> avoid converting to Java string
kares committed Aug 28, 2017

Unverified

This user has not yet uploaded their public signing key.
1 parent 112e365 commit 9878cf0
Showing 8 changed files with 93 additions and 55 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyArgsFile.java
Original file line number Diff line number Diff line change
@@ -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");
}
6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/RubyFile.java
Original file line number Diff line number Diff line change
@@ -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)
81 changes: 48 additions & 33 deletions core/src/main/java/org/jruby/RubyIO.java
Original file line number Diff line number Diff line change
@@ -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;
@@ -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";

@@ -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;
@@ -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;
@@ -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);
@@ -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;
@@ -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();
@@ -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);
@@ -2080,7 +2079,7 @@ protected IRubyObject rbIoClose(Ruby runtime) {
if (locked) fptr.unlock();
}

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

// MRI: rb_io_close_write
@@ -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;
}

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

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

@@ -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;
}

@@ -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)
@@ -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;
@@ -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) {
@@ -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");
}

@@ -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);
}
}

@@ -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() {
@@ -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;
}
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyKernel.java
Original file line number Diff line number Diff line change
@@ -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;
}
3 changes: 1 addition & 2 deletions core/src/main/java/org/jruby/ext/socket/RubyBasicSocket.java
Original file line number Diff line number Diff line change
@@ -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
12 changes: 5 additions & 7 deletions core/src/main/java/org/jruby/ext/socket/RubySocket.java
Original file line number Diff line number Diff line change
@@ -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
16 changes: 8 additions & 8 deletions core/src/main/java/org/jruby/ext/tempfile/Tempfile.java
Original file line number Diff line number Diff line change
@@ -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;
@@ -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)
@@ -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);

@@ -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());
}

26 changes: 26 additions & 0 deletions spec/java_integration/addons/io_spec.rb
Original file line number Diff line number Diff line change
@@ -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
@@ -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)

0 comments on commit 9878cf0

Please sign in to comment.