diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java index c09506f6464642f6048f9a925fd299f5b9f6ca90..f3119ba912260d9230d786fa75254fe960238358 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java @@ -267,6 +267,8 @@ public class BasePackage extends RBuiltinPackage { add(ConnectionFunctions.SocketConnection.class, ConnectionFunctionsFactory.SocketConnectionNodeGen::create); add(ConnectionFunctions.RawConnection.class, ConnectionFunctionsFactory.RawConnectionNodeGen::create); add(ConnectionFunctions.RawConnectionValue.class, ConnectionFunctionsFactory.RawConnectionValueNodeGen::create); + add(ConnectionFunctions.Fifo.class, ConnectionFunctionsFactory.FifoNodeGen::create); + add(ConnectionFunctions.Pipe.class, ConnectionFunctionsFactory.PipeNodeGen::create); add(ConnectionFunctions.Stderr.class, ConnectionFunctionsFactory.StderrNodeGen::create); add(ConnectionFunctions.Stdin.class, ConnectionFunctionsFactory.StdinNodeGen::create); add(ConnectionFunctions.Stdout.class, ConnectionFunctionsFactory.StdoutNodeGen::create); @@ -277,6 +279,7 @@ public class BasePackage extends RBuiltinPackage { add(ConnectionFunctions.WriteBin.class, ConnectionFunctionsFactory.WriteBinNodeGen::create); add(ConnectionFunctions.WriteChar.class, ConnectionFunctionsFactory.WriteCharNodeGen::create); add(ConnectionFunctions.WriteLines.class, ConnectionFunctionsFactory.WriteLinesNodeGen::create); + add(ConnectionFunctions.IsIncomplete.class, ConnectionFunctionsFactory.IsIncompleteNodeGen::create); add(Contributors.class, ContributorsNodeGen::create); add(CopyDFAttr.class, CopyDFAttrNodeGen::create); add(Crossprod.class, CrossprodNodeGen::create); diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Capabilities.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Capabilities.java index 109ddc233bd053d03bc66ec90a3f0fc9d9e0e74b..b63bb9fb4ee8b1ca853fe580049c8310600be019 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Capabilities.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Capabilities.java @@ -46,7 +46,7 @@ public abstract class Capabilities extends RBuiltinNode { http_fttp(true, "http/ftp"), sockets(true, null), libxml(false, null), - fifo(false, null), + fifo(true, null), cledit(false, null), iconv(false, null), nls(false, "NLS"), diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java index c5c88dd46deb72138752c13de9f9f74a7dbb5f01..30aa8c4efb3bddaa7fb3ba973fa57c835891e778 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java @@ -49,10 +49,12 @@ import static com.oracle.truffle.r.runtime.conn.StdConnections.getStdout; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.DoubleBuffer; import java.nio.IntBuffer; +import java.nio.charset.IllegalCharsetNameException; import java.util.ArrayList; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -63,15 +65,18 @@ import com.oracle.truffle.r.nodes.builtin.NodeWithArgumentCasts.Casts; import com.oracle.truffle.r.nodes.builtin.RBuiltinNode; import com.oracle.truffle.r.nodes.builtin.base.ConnectionFunctionsFactory.WriteDataNodeGen; import com.oracle.truffle.r.nodes.builtin.casts.fluent.HeadPhaseBuilder; +import com.oracle.truffle.r.nodes.builtin.casts.fluent.InitialPhaseBuilder; import com.oracle.truffle.r.runtime.RCompression; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.RError.Message; import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.builtins.RBuiltin; -import com.oracle.truffle.r.runtime.conn.CompressedConnections.CompressedRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.conn.FifoConnections.FifoRConnection; +import com.oracle.truffle.r.runtime.conn.FileConnections.CompressedRConnection; import com.oracle.truffle.r.runtime.conn.FileConnections.FileRConnection; +import com.oracle.truffle.r.runtime.conn.PipeConnections.PipeRConnection; import com.oracle.truffle.r.runtime.conn.RConnection; import com.oracle.truffle.r.runtime.conn.RawConnections.RawRConnection; import com.oracle.truffle.r.runtime.conn.SocketConnections.RSocketConnection; @@ -139,8 +144,16 @@ public abstract class ConnectionFunctions { } public static final class CastsHelper { - private static void description(Casts casts) { - casts.arg("description").mustBe(stringValue()).asStringVector().shouldBe(singleElement(), RError.Message.ARGUMENT_ONLY_FIRST_1, "description").findFirst().mustNotBeNA(); + private static HeadPhaseBuilder<String> description(Casts casts) { + return descriptionInternal(casts.arg("description")); + } + + private static HeadPhaseBuilder<String> descriptionNull(Casts casts) { + return descriptionInternal(casts.arg("description").allowNull()); + } + + private static HeadPhaseBuilder<String> descriptionInternal(InitialPhaseBuilder<Object> casts) { + return casts.mustBe(stringValue()).asStringVector().shouldBe(singleElement(), RError.Message.ARGUMENT_ONLY_FIRST_1, "description").findFirst().mustNotBeNA(); } private static HeadPhaseBuilder<String> open(Casts casts) { @@ -189,7 +202,11 @@ public abstract class ConnectionFunctions { } private static void method(Casts casts) { - casts.arg("method").asStringVector().findFirst(); + casts.arg("method").asStringVector().findFirst().mustBe(equalTo("default").or(equalTo("internal")), RError.Message.UNSUPPORTED_URL_METHOD); + } + + static void blockingNotSupported(Casts casts) { + casts.arg("blocking").asLogicalVector().findFirst().mustBe(logicalTrue(), RError.Message.NYI, "non-blocking mode not supported").map(toBoolean()); } } @@ -200,7 +217,7 @@ public abstract class ConnectionFunctions { Casts casts = new Casts(File.class); CastsHelper.description(casts); CastsHelper.open(casts); - casts.arg("blocking").asLogicalVector().findFirst().mustBe(logicalTrue(), RError.Message.NYI, "non-blocking mode not supported").map(toBoolean()); + CastsHelper.blocking(casts); CastsHelper.encoding(casts); CastsHelper.method(casts); CastsHelper.raw(casts); @@ -208,11 +225,25 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary - @SuppressWarnings("unused") - protected RAbstractIntVector file(String description, String openArg, boolean blocking, String encoding, String method, boolean raw) { + protected RAbstractIntVector file(String description, String openArg, boolean blocking, String encoding, @SuppressWarnings("unused") String method, boolean raw) { String open = openArg; - // TODO handle http/ftp prefixes and redirect and method - String path = removeFileURLPrefix(description); + + // check if the description is an URL and dispatch if necessary + String path = description; + try { + URL url = new URL(description); + if (!"file".equals(url.getProtocol())) { + return new URLRConnection(description, open, encoding).asVector(); + } else { + path = removeFileURLPrefix(description); + } + } catch (MalformedURLException e) { + // ignore and try to open file + } catch (IOException e) { + RError.warning(RError.SHOW_CALLER, RError.Message.UNABLE_TO_RESOLVE, e.getMessage()); + throw RError.error(RError.SHOW_CALLER, RError.Message.CANNOT_OPEN_CONNECTION); + } + if (path.length() == 0) { // special case, temp file opened in "w+" or "w+b" only if (open.length() == 0) { @@ -225,10 +256,12 @@ public abstract class ConnectionFunctions { } } try { - return new FileRConnection(path, open).asVector(); + return new FileRConnection(description, path, open, blocking, encoding, raw).asVector(); } catch (IOException ex) { warning(RError.Message.CANNOT_OPEN_FILE, description, ex.getMessage()); throw error(RError.Message.CANNOT_OPEN_CONNECTION); + } catch (IllegalCharsetNameException ex) { + throw error(RError.Message.UNSUPPORTED_ENCODING_CONVERSION, encoding, ""); } } } @@ -256,11 +289,13 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary - protected RAbstractIntVector zzFile(RAbstractStringVector description, String open, String encoding, int compression) { + protected RAbstractIntVector zzFile(String description, String open, String encoding, int compression) { try { - return new CompressedRConnection(description.getDataAt(0), open, cType, encoding, compression).asVector(); + return new CompressedRConnection(description, open, cType, encoding, compression).asVector(); } catch (IOException ex) { - throw reportError(description.getDataAt(0), ex); + throw reportError(description, ex); + } catch (IllegalCharsetNameException ex) { + throw RError.error(RError.SHOW_CALLER, RError.Message.UNSUPPORTED_ENCODING_CONVERSION, encoding, ""); } } @@ -308,10 +343,7 @@ public abstract class ConnectionFunctions { static { Casts casts = new Casts(TextConnection.class); - CastsHelper.description(casts); - // TODO how to have either a RNull or a String/RStringVector and have the latter coerced - // to a - // RAbstractStringVector to avoid the explicit handling in the specialization + CastsHelper.descriptionNull(casts); casts.arg("text").allowNull().mustBe(stringValue()); CastsHelper.open(casts).mustBe(equalTo("").or(equalTo("r").or(equalTo("w").or(equalTo("a")))), RError.Message.UNSUPPORTED_MODE); casts.arg("env").mustNotBeNull(RError.Message.USE_NULL_ENV_DEFUNCT).mustBe(instanceOf(REnvironment.class)); @@ -328,15 +360,9 @@ public abstract class ConnectionFunctions { } } - @SuppressWarnings("unused") @Specialization - @TruffleBoundary - protected RAbstractIntVector textConnection(String description, RNull text, String open, REnvironment env, int encoding) { - if (open.length() == 0 || open.equals("r")) { - throw error(RError.Message.INVALID_ARGUMENT, "text"); - } else { - throw RError.nyi(RError.SHOW_CALLER, "textConnection: NULL"); - } + protected RAbstractIntVector textConnection(String description, @SuppressWarnings("unused") RNull text, String open, REnvironment env, int encoding) { + return textConnection(description, (RAbstractStringVector) null, open, env, encoding); } } @@ -350,10 +376,10 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary - protected Object textConnection(int con) { + protected RAbstractStringVector textConnection(int con) { RConnection connection = RConnection.fromIndex(con); if (connection instanceof TextRConnection) { - return RDataFactory.createStringVector(((TextRConnection) connection).getValue(), RDataFactory.COMPLETE_VECTOR); + return ((TextRConnection) connection).getValue(); } else { throw error(Message.NOT_A_TEXT_CONNECTION); } @@ -375,11 +401,14 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary - protected RAbstractIntVector socketConnection(String host, int port, boolean server, boolean blocking, String open, @SuppressWarnings("unused") RAbstractStringVector encoding, int timeout) { + protected RAbstractIntVector socketConnection(String host, int port, boolean server, boolean blocking, String open, + String encoding, int timeout) { try { - return new RSocketConnection(open, server, host, port, blocking, timeout).asVector(); + return new RSocketConnection(open, server, host, port, blocking, timeout, encoding).asVector(); } catch (IOException ex) { throw error(RError.Message.CANNOT_OPEN_CONNECTION); + } catch (IllegalCharsetNameException ex) { + throw error(RError.Message.UNSUPPORTED_ENCODING_CONVERSION, encoding, ""); } } } @@ -398,14 +427,16 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary - protected RAbstractIntVector urlConnection(String url, String open, @SuppressWarnings("unused") boolean blocking, @SuppressWarnings("unused") String encoding, + protected RAbstractIntVector urlConnection(String url, String open, @SuppressWarnings("unused") boolean blocking, String encoding, @SuppressWarnings("unused") String method) { try { - return new URLRConnection(url, open).asVector(); + return new URLRConnection(url, open, encoding).asVector(); } catch (MalformedURLException ex) { throw error(RError.Message.UNSUPPORTED_URL_SCHEME); } catch (IOException ex) { throw error(RError.Message.CANNOT_OPEN_CONNECTION); + } catch (IllegalCharsetNameException ex) { + throw error(RError.Message.UNSUPPORTED_ENCODING_CONVERSION, encoding, ""); } } } @@ -452,7 +483,7 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary protected Object textConnection(int con) { - RConnection connection = RConnection.fromIndex(con); + BaseRConnection connection = RConnection.fromIndex(con); if (connection instanceof RawRConnection) { return RDataFactory.createRawVector(((RawRConnection) connection).getValue()); } else { @@ -585,7 +616,7 @@ public abstract class ConnectionFunctions { @Specialization @TruffleBoundary protected Object readLines(int con, int n, boolean ok, boolean warn, @SuppressWarnings("unused") String encoding, boolean skipNul) { - // TODO implement all the arguments + // TODO Implement argument 'encoding'. try (RConnection openConn = RConnection.fromIndex(con).forceOpen("rt")) { String[] lines = openConn.readLines(n, warn, skipNul); if (n > 0 && lines.length < n && !ok) { @@ -708,7 +739,7 @@ public abstract class ConnectionFunctions { @Specialization(guards = "!ncharsEmpty(nchars)") @TruffleBoundary protected RStringVector readChar(int con, RAbstractIntVector nchars, boolean useBytes) { - try (RConnection openConn = RConnection.fromIndex(con).forceOpen("rb")) { + try (BaseRConnection openConn = RConnection.fromIndex(con).forceOpen("rb")) { String[] data = new String[nchars.getLength()]; for (int i = 0; i < data.length; i++) { data[i] = openConn.readChar(nchars.getDataAt(i), useBytes); @@ -748,10 +779,10 @@ public abstract class ConnectionFunctions { @TruffleBoundary private RNull writeCharGeneric(RAbstractStringVector object, int con, RAbstractIntVector nchars, RAbstractStringVector sep, boolean useBytes) { try (RConnection openConn = RConnection.fromIndex(con).forceOpen("wb")) { - int length = object.getLength(); + final int length = object.getLength(); + final int ncharsLen = nchars.getLength(); for (int i = 0; i < length; i++) { - // FIXME: 'i % length' is probably wrong - int nc = nchars.getDataAt(i % length); + int nc = nchars.getDataAt(i % ncharsLen); String s = object.getDataAt(i); final int writeLen = Math.min(s.length(), nc); int pad = nc - s.length(); @@ -1187,13 +1218,15 @@ public abstract class ConnectionFunctions { * for the NA (enquiry) case. */ long offset = 0; + final int actualOrigin; if (RRuntime.isNAorNaN(where)) { - origin = 0; + actualOrigin = 0; } else { offset = (long) where; + actualOrigin = origin; } try { - long newOffset = RConnection.fromIndex(con).seek(offset, RConnection.SeekMode.values()[origin], RConnection.SeekRWMode.values()[rw]); + long newOffset = RConnection.fromIndex(con).seek(offset, RConnection.SeekMode.values()[actualOrigin], RConnection.SeekRWMode.values()[rw]); if (newOffset > Integer.MAX_VALUE) { throw RError.nyi(RError.SHOW_CALLER, "seek > Integer.MAX_VALUE"); } @@ -1203,4 +1236,76 @@ public abstract class ConnectionFunctions { } } } + + @RBuiltin(name = "fifo", kind = INTERNAL, parameterNames = {"description", "open", "blocking", "encoding"}, behavior = IO) + public abstract static class Fifo extends RBuiltinNode { + + static { + Casts casts = new Casts(Fifo.class); + CastsHelper.description(casts); + CastsHelper.open(casts); + // We cannot support non-blocking because Java does simply not allow to open a file or + // named pipe in non-blocking mode. + CastsHelper.blockingNotSupported(casts); + CastsHelper.encoding(casts); + } + + @Specialization + @TruffleBoundary + protected RAbstractIntVector fifo(String path, String openArg, boolean blocking, String encoding) { + + String open = openArg; + try { + return new FifoRConnection(path, open, blocking, encoding).asVector(); + } catch (IOException ex) { + RError.warning(this, RError.Message.CANNOT_OPEN_FIFO, path); + throw RError.error(RError.SHOW_CALLER, RError.Message.CANNOT_OPEN_CONNECTION); + } catch (IllegalCharsetNameException ex) { + throw RError.error(RError.SHOW_CALLER, RError.Message.UNSUPPORTED_ENCODING_CONVERSION, encoding, ""); + } + } + } + + @RBuiltin(name = "pipe", kind = INTERNAL, parameterNames = {"description", "open", "encoding"}, behavior = IO) + public abstract static class Pipe extends RBuiltinNode { + + static { + Casts casts = new Casts(Pipe.class); + CastsHelper.description(casts); + CastsHelper.open(casts); + CastsHelper.encoding(casts); + } + + @Specialization + @TruffleBoundary + protected RAbstractIntVector pipe(String path, String openArg, String encoding) { + + String open = openArg; + try { + return new PipeRConnection(path, open, encoding).asVector(); + } catch (IOException ex) { + RError.warning(this, RError.Message.CANNOT_OPEN_FIFO, path); + throw RError.error(RError.SHOW_CALLER, RError.Message.CANNOT_OPEN_CONNECTION); + } catch (IllegalCharsetNameException ex) { + throw RError.error(RError.SHOW_CALLER, RError.Message.UNSUPPORTED_ENCODING_CONVERSION, encoding, ""); + } + } + } + + @RBuiltin(name = "isIncomplete", kind = INTERNAL, parameterNames = {"con"}, behavior = IO) + public abstract static class IsIncomplete extends RBuiltinNode { + + static { + Casts casts = new Casts(IsIncomplete.class); + CastsHelper.connection(casts); + } + + @Specialization + @TruffleBoundary + protected RLogicalVector isIncomplete(int con) { + + final boolean res = RConnection.fromIndex(con).isIncomplete(); + return RDataFactory.createLogicalVectorFromScalar(res); + } + } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCompression.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCompression.java index a1d9f81682824f9db676bb090289431879662522..b80c2f0d91394557a75151b63c341b280c201d4b 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCompression.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCompression.java @@ -212,6 +212,8 @@ public class RCompression { if (rc == 0) { readThread.join(); return Arrays.copyOf(readThread.getData(), readThread.getTotalRead()); + } else { + throw new IOException("bzip2 error code: " + rc); } } catch (InterruptedException ex) { // fall through @@ -239,6 +241,8 @@ public class RCompression { OpenOption[] openOptions = append ? new OpenOption[]{StandardOpenOption.APPEND} : new OpenOption[0]; Files.write(Paths.get(path), cData, openOptions); return; + } else { + throw new IOException("bzip2 error code: " + rc); } } catch (InterruptedException ex) { // fall through diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java index ad5120700f477af7b7e2fdb65e2feed6b8c62db3..09e7edf54a8abe767c81b58b44c309bd17696021 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java @@ -373,6 +373,7 @@ public final class RError extends RuntimeException { ONLY_WRITE_BINARY_CONNECTION("can only write to a binary connection"), ONLY_WRITE_CHAR_OBJECTS("can only write character objects"), NOT_A_TEXT_CONNECTION("'con' is not a textConnection"), + NOT_AN_OUTPUT_TEXT_CONNECTION("'con' is not an output textConnection"), UNSEEKABLE_CONNECTION("'con' is not seekable"), MUST_BE_STRING_OR_CONNECTION("'%s' must be a character string or a connection"), MORE_CHARACTERS("writeChar: more characters requested than are in the string - will zero-pad"), @@ -831,7 +832,14 @@ public final class RError extends RuntimeException { NOT_AN_OUTPUT_RAW_CONNECTION("'con' is not an output rawConnection"), NOT_A_RAW_CONNECTION("'con' is not a rawConnection"), SEEK_OUTSITE_RAW_CONNECTION("attempt to seek outside the range of the raw connection"), - VECTOR_IS_TOO_LARGE("vector is too large"); + VECTOR_IS_TOO_LARGE("vector is too large"), + SEEK_NOT_RELEVANT_FOR_TEXT_CON("seek is not relevant for text connection"), + NOT_ENABLED_FOR_THIS_CONN("'%s' not enabled for this connection"), + CANNOT_OPEN_FIFO("cannot open fifo '%s'"), + UNSUPPORTED_ENCODING_CONVERSION("unsupported conversion from '%s' to '%s'"), + UNABLE_TO_RESOLVE("unable to resolve '%s'"), + LINE_CONTAINS_EMBEDDED_NULLS("line %d appears to contain an embedded nul"), + UNSUPPORTED_URL_METHOD("method = \"%s\" is not supported"); public final String message; final boolean hasArgs; diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/CompressedConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/CompressedConnections.java deleted file mode 100644 index 7bf6d1af13b9fb97d677e675c67c74b63b24618f..0000000000000000000000000000000000000000 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/CompressedConnections.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.r.runtime.conn; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.tukaani.xz.LZMA2Options; -import org.tukaani.xz.XZ; -import org.tukaani.xz.XZInputStream; -import org.tukaani.xz.XZOutputStream; - -import com.oracle.truffle.r.runtime.RCompression; -import com.oracle.truffle.r.runtime.RCompression.Type; -import com.oracle.truffle.r.runtime.RError; -import com.oracle.truffle.r.runtime.RInternalError; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BasePathRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateWriteRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ReadWriteHelper; -import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; - -public class CompressedConnections { - public static final int GZIP_BUFFER_SIZE = (2 << 20); - - /** - * Base class for all modes of gzfile/bzfile/xzfile connections. N.B. In GNU R these can read - * gzip, bzip, lzma and uncompressed files, and this has to be implemented by reading the first - * few bytes of the file and detecting the type of the file. - */ - public static class CompressedRConnection extends BasePathRConnection { - private final RCompression.Type cType; - @SuppressWarnings("unused") private final String encoding; // TODO - @SuppressWarnings("unused") private final int compression; // TODO - - public CompressedRConnection(String path, String modeString, Type cType, String encoding, int compression) throws IOException { - super(path, mapConnectionClass(cType), modeString, AbstractOpenMode.ReadBinary); - this.cType = cType; - this.encoding = encoding; - this.compression = compression; - openNonLazyConnection(); - } - - private static ConnectionClass mapConnectionClass(RCompression.Type cType) { - switch (cType) { - case NONE: - return ConnectionClass.File; - case GZIP: - return ConnectionClass.GZFile; - case BZIP2: - return ConnectionClass.BZFile; - case XZ: - return ConnectionClass.XZFile; - default: - throw RInternalError.shouldNotReachHere(); - } - } - - @Override - protected void createDelegateConnection() throws IOException { - DelegateRConnection delegate = null; - AbstractOpenMode openMode = getOpenMode().abstractOpenMode; - switch (openMode) { - case Read: - case ReadBinary: - /* - * For input, we check the actual compression type as GNU R is permissive about - * the claimed type. - */ - RCompression.Type cTypeActual = RCompression.getCompressionType(path); - if (cTypeActual != cType) { - updateConnectionClass(mapConnectionClass(cTypeActual)); - } - switch (cTypeActual) { - case NONE: - if (openMode == AbstractOpenMode.ReadBinary) { - delegate = new FileConnections.FileReadBinaryRConnection(this); - } else { - delegate = new FileConnections.FileReadTextRConnection(this); - } - break; - case GZIP: - delegate = new CompressedInputRConnection(this, new GZIPInputStream(new FileInputStream(path), GZIP_BUFFER_SIZE)); - break; - case XZ: - delegate = new CompressedInputRConnection(this, new XZInputStream(new FileInputStream(path))); - break; - case BZIP2: - // no in Java support, so go via byte array - byte[] bzipUdata = RCompression.bzipUncompressFromFile(path); - delegate = new ByteStreamCompressedInputRConnection(this, new ByteArrayInputStream(bzipUdata)); - } - break; - - case Append: - case AppendBinary: - case Write: - case WriteBinary: { - boolean append = openMode == AbstractOpenMode.Append || openMode == AbstractOpenMode.AppendBinary; - switch (cType) { - case GZIP: - delegate = new CompressedOutputRConnection(this, new GZIPOutputStream(new FileOutputStream(path, append), GZIP_BUFFER_SIZE)); - break; - case BZIP2: - delegate = new BZip2OutputRConnection(this, new ByteArrayOutputStream(), append); - break; - case XZ: - delegate = new CompressedOutputRConnection(this, new XZOutputStream(new FileOutputStream(path, append), new LZMA2Options(), XZ.CHECK_CRC32)); - break; - } - break; - } - default: - throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + getOpenMode()); - } - setDelegate(delegate); - } - - // @Override - /** - * GnuR behavior for lazy connections is odd, e.g. gzfile returns "text", even though the - * default mode is "rb". - */ - // public boolean isTextMode() { - // } - } - - private static class CompressedInputRConnection extends DelegateReadRConnection implements ReadWriteHelper { - private final InputStream inputStream; - - protected CompressedInputRConnection(CompressedRConnection base, InputStream is) { - super(base); - this.inputStream = is; - } - - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, inputStream, useBytes); - } - - @Override - public int readBin(ByteBuffer buffer) throws IOException { - return readBinHelper(buffer, inputStream); - } - - @Override - public byte[] readBinChars() throws IOException { - return readBinCharsHelper(inputStream); - } - - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - return readLinesHelper(inputStream, n, warn, skipNul); - } - - @Override - public InputStream getInputStream() throws IOException { - return inputStream; - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); - } - - @Override - public void close() throws IOException { - inputStream.close(); - } - } - - private static class ByteStreamCompressedInputRConnection extends CompressedInputRConnection { - ByteStreamCompressedInputRConnection(CompressedRConnection base, ByteArrayInputStream is) { - super(base, is); - } - } - - private static class CompressedOutputRConnection extends DelegateWriteRConnection implements ReadWriteHelper { - protected OutputStream outputStream; - - protected CompressedOutputRConnection(CompressedRConnection base, OutputStream os) { - super(base); - this.outputStream = os; - } - - @Override - public OutputStream getOutputStream() throws IOException { - return outputStream; - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); - } - - @Override - public void close() throws IOException { - flush(); - outputStream.close(); - } - - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - writeLinesHelper(outputStream, lines, sep); - } - - @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - writeCharHelper(outputStream, s, pad, eos); - } - - @Override - public void writeBin(ByteBuffer buffer) throws IOException { - writeBinHelper(buffer, outputStream); - } - - @Override - public void writeString(String s, boolean nl) throws IOException { - writeStringHelper(outputStream, s, nl); - } - - @Override - public void flush() throws IOException { - outputStream.flush(); - } - } - - private static class BZip2OutputRConnection extends CompressedOutputRConnection { - private final ByteArrayOutputStream bos; - private final boolean append; - - BZip2OutputRConnection(CompressedRConnection base, ByteArrayOutputStream os, boolean append) { - super(base, os); - this.bos = os; - this.append = append; - } - - @Override - public void close() throws IOException { - flush(); - outputStream.close(); - // Now actually do the compression using sub-process - byte[] data = bos.toByteArray(); - RCompression.bzipCompressToFile(data, ((BasePathRConnection) base).path, append); - } - } -} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java index 3eb19bd3cc339cc4ae780f1c27a3fa92c6dab278..3e0941dce29b4a1325dbe39163aad99d1a9b039a 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java @@ -22,7 +22,6 @@ */ package com.oracle.truffle.r.runtime.conn; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -30,15 +29,29 @@ import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.RError.Message; import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.Utils; import com.oracle.truffle.r.runtime.context.RContext; +import com.oracle.truffle.r.runtime.data.RAttributesLayout; import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RExternalPtr; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; @@ -107,7 +120,7 @@ public class ConnectionSupport { for (int i = 0; i <= hwm; i++) { WeakReference<BaseRConnection> ref = allConnections.get(i); if (ref != null) { - BaseRConnection con = ref.get(); + RConnection con = ref.get(); if (con != null) { list.add(i); } @@ -319,7 +332,9 @@ public class ConnectionSupport { Text("textConnection"), URL("url"), RAW("rawConnection"), - Internal("internal"); + Internal("internal"), + PIPE("pipe"), + FIFO("fifo"); private final String printName; @@ -342,19 +357,12 @@ public class ConnectionSupport { } } - // TODO implement all open modes - - public static final class InvalidConnection extends RConnection { + public static final class InvalidConnection implements RConnection { public static final InvalidConnection instance = new InvalidConnection(); private static final int INVALID_DESCRIPTOR = -1; - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); - } - @Override public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); @@ -400,11 +408,6 @@ public class ConnectionSupport { throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); } - @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { - throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); - } - @Override public int getc() throws IOException { throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); @@ -464,6 +467,21 @@ public class ConnectionSupport { public boolean isOpen() { throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); } + + @Override + public void pushBack(RAbstractStringVector lines, boolean addNewLine) { + throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); + } + + @Override + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); + } + + @Override + public ByteChannel getChannel() throws IOException { + throw RInternalError.shouldNotReachHere("INVALID CONNECTION"); + } } /** @@ -485,7 +503,7 @@ public class ConnectionSupport { * it subsequently will throw an error. The latter will open/close the connection (internally) * and this can be repeated indefinitely. */ - public abstract static class BaseRConnection extends RConnection { + public abstract static class BaseRConnection implements RConnection { /** * {@code true} is the connection has been opened successfully. N.B. This supports lazy @@ -519,21 +537,84 @@ public class ConnectionSupport { */ private int descriptor; + private boolean blocking = true; + + /** + * The encoding to use to read or to write to the connection. + */ + private Charset encoding; + private ConnectionClass conClass; + private LinkedList<String> pushBack; + + /** + * Indicates that the last line read operation was incomplete.<br> + * This is only relevant for connections in text and non-blocking mode. + */ + private boolean incomplete = false; + /** * The constructor for every connection class except {@link StdConnections}. * * @param conClass the specific class of the connection, e.g, {@link ConnectionClass#File} * @param modeString the mode in which the connection should be opened, "" for lazy opening * @param defaultModeForLazy the mode to use when this connection is opened implicitly + * @param blocking Indicates if this connection has been openend in blocking mode. + * @param encoding The name of the encoding used to read from or to write to the connection + * ({@code null} is allowed and sets the character set to + * {@code Charset#defaultCharset()}). * */ - protected BaseRConnection(ConnectionClass conClass, String modeString, AbstractOpenMode defaultModeForLazy) throws IOException { + protected BaseRConnection(ConnectionClass conClass, String modeString, AbstractOpenMode defaultModeForLazy, boolean blocking, String encoding) throws IOException, IllegalCharsetNameException { this(conClass, new OpenMode(modeString, defaultModeForLazy)); if (conClass != ConnectionClass.Internal) { this.descriptor = getContextStateImpl().setConnection(this); } + this.blocking = blocking; + if (encoding != null) { + this.encoding = Charset.forName(ConnectionSupport.convertEncodingName(encoding)); + } else { + this.encoding = Charset.defaultCharset(); + } + } + + /** + * The constructor for every connection class except {@link StdConnections}. + * + * @param conClass the specific class of the connection, e.g, {@link ConnectionClass#File} + * @param modeString the mode in which the connection should be opened, "" for lazy opening + * @param defaultModeForLazy the mode to use when this connection is opened implicitly + * @param blocking Indicates if this connection has been openend in blocking mode. + * + */ + protected BaseRConnection(ConnectionClass conClass, String modeString, AbstractOpenMode defaultModeForLazy, boolean blocking) throws IOException { + this(conClass, modeString, defaultModeForLazy, blocking, null); + } + + /** + * The constructor for every connection class except {@link StdConnections}. + * + * @param conClass the specific class of the connection, e.g, {@link ConnectionClass#File} + * @param modeString the mode in which the connection should be opened, "" for lazy opening + * @param defaultModeForLazy the mode to use when this connection is opened implicitly + * @param encoding The name of the encoding used to read from or to write to the connection. + * + */ + protected BaseRConnection(ConnectionClass conClass, String modeString, AbstractOpenMode defaultModeForLazy, String encoding) throws IOException { + this(conClass, modeString, defaultModeForLazy, true, encoding); + } + + /** + * The constructor for every connection class except {@link StdConnections}. + * + * @param conClass the specific class of the connection, e.g, {@link ConnectionClass#File} + * @param modeString the mode in which the connection should be opened, "" for lazy opening + * @param defaultModeForLazy the mode to use when this connection is opened implicitly + * + */ + protected BaseRConnection(ConnectionClass conClass, String modeString, AbstractOpenMode defaultModeForLazy) throws IOException { + this(conClass, modeString, defaultModeForLazy, true, null); } /** @@ -611,8 +692,19 @@ public class ConnectionSupport { return getOpenMode().isText(); } + public boolean isBlocking() { + return blocking; + } + + /** + * Returns the original encoding string. + */ + public Charset getEncoding() { + return encoding; + } + @Override - public RConnection forceOpen(String modeString) throws IOException { + public BaseRConnection forceOpen(String modeString) throws IOException { if (closed) { throw new IOException(RError.Message.INVALID_CONNECTION.message); } @@ -648,10 +740,9 @@ public class ConnectionSupport { opened = true; } - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { + protected String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { checkOpen(); - return theConnection.readLinesInternal(n, warn, skipNul); + return theConnection.readLines(n, warn, skipNul); } @Override @@ -666,6 +757,12 @@ public class ConnectionSupport { return theConnection.getOutputStream(); } + @Override + public ByteChannel getChannel() throws IOException { + checkOpen(); + return theConnection.getChannel(); + } + @Override public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { checkOpen(); @@ -742,8 +839,7 @@ public class ConnectionSupport { return theConnection.getc(); } - @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + public long seekInternal(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { checkOpen(); return theConnection.seek(offset, seekMode, seekRWMode); } @@ -786,349 +882,290 @@ public class ConnectionSupport { public boolean isClosed() { return closed; } - } - - public static BaseRConnection getBaseConnection(RConnection conn) { - if (conn instanceof BaseRConnection) { - return (BaseRConnection) conn; - } else if (conn instanceof DelegateReadRConnection) { - return ((DelegateReadRConnection) conn).base; - } else { - throw RInternalError.shouldNotReachHere(); - } - } - - interface ReadWriteHelper { - /** - * {@code readLines} from an {@link InputStream}. It would be convenient to use a - * {@link BufferedReader} but mixing binary and text operations, which is a requirement, - * would then be difficult. - * - * @param warn TODO - * @param skipNul TODO - */ - default String[] readLinesHelper(InputStream in, int n, boolean warn, boolean skipNul) throws IOException { - ArrayList<String> lines = new ArrayList<>(); - int totalRead = 0; - byte[] buffer = new byte[64]; - int pushBack = 0; - while (true) { - int ch; - if (pushBack != 0) { - ch = pushBack; - pushBack = 0; - } else { - ch = in.read(); - } - boolean lineEnd = false; - if (ch < 0) { - if (totalRead > 0) { - /* - * TODO GnuR says keep data and output a warning if blocking, otherwise - * silently push back. FastR doesn't support non-blocking yet, so we keep - * the data. Some refactoring is needed to be able to reliably access the - * "name" for the warning. - */ - lines.add(new String(buffer, 0, totalRead)); - if (warn) { - RError.warning(RError.SHOW_CALLER2, RError.Message.INCOMPLETE_FINAL_LINE, "TODO: connection path"); - } - } - break; - } - if (ch == '\n') { - lineEnd = true; - } else if (ch == '\r') { - lineEnd = true; - ch = in.read(); - if (ch == '\n') { - // swallow the trailing lf - } else { - pushBack = ch; - } - } - if (lineEnd) { - lines.add(new String(buffer, 0, totalRead)); - if (n > 0 && lines.size() == n) { - break; + private String readOneLineWithPushBack(List<String> res, int ind) { + String s = pushBack.pollLast(); + if (s == null) { + return null; + } else { + String[] lines = s.split("\n", 2); + if (lines.length == 2) { + // we hit end of the line + if (lines[1].length() != 0) { + // suffix is not empty and needs to be processed later + pushBack.push(lines[1]); } - totalRead = 0; + assert res.size() == ind; + res.add(ind, lines[0]); + return null; } else { - buffer = checkBuffer(buffer, totalRead); - buffer[totalRead++] = (byte) (ch & 0xFF); - } - } - String[] result = new String[lines.size()]; - lines.toArray(result); - return result; - } - - default void writeLinesHelper(OutputStream out, RAbstractStringVector lines, String sep) throws IOException { - for (int i = 0; i < lines.getLength(); i++) { - out.write(lines.getDataAt(i).getBytes()); - out.write(sep.getBytes()); - } - } - - default void writeStringHelper(OutputStream out, String s, boolean nl) throws IOException { - out.write(s.getBytes()); - if (nl) { - out.write('\n'); - } - } + // no end of the line found yet + StringBuilder sb = new StringBuilder(); + do { + assert lines.length == 1; + sb.append(lines[0]); + s = pushBack.pollLast(); + if (s == null) { + break; + } - default void writeCharHelper(OutputStream out, String s, int pad, String eos) throws IOException { - out.write(s.getBytes()); - if (pad > 0) { - for (int i = 0; i < pad; i++) { - out.write(0); - } - } - if (eos != null) { - if (eos.length() > 0) { - out.write(eos.getBytes()); + lines = s.split("\n", 2); + if (lines.length == 2) { + // we hit end of the line + if (lines[1].length() != 0) { + // suffix is not empty and needs to be processed later + pushBack.push(lines[1]); + } + assert res.size() == ind; + res.add(ind, sb.append(lines[0]).toString()); + return null; + } // else continue + } while (true); + return sb.toString(); } - // function writeChar is defined to append the null character if eos != null - out.write(0); } } - default void writeBinHelper(ByteBuffer buffer, OutputStream outputStream) throws IOException { - int n = buffer.remaining(); - byte[] b = new byte[n]; - buffer.get(b); - outputStream.write(b); - } - /** - * Reads null-terminated character strings from an {@link InputStream}. + * TODO(fa) This method is subject for refactoring. I modified the original implementation + * to be capable to handle a negative 'n' parameter. It indicates to read as much lines as + * available. */ - default byte[] readBinCharsHelper(InputStream in) throws IOException { - int ch = in.read(); - if (ch < 0) { - return null; - } - int totalRead = 0; - byte[] buffer = new byte[64]; - while (true) { - buffer = checkBuffer(buffer, totalRead); - buffer[totalRead++] = (byte) (ch & 0xFF); - if (ch == 0) { - break; - } - ch = in.read(); + @TruffleBoundary + private String[] readLinesWithPushBack(int n, boolean warn, boolean skipNul) throws IOException { + // NOTE: 'n' may be negative indicating to read as much lines as available + final List<String> res; + if (n >= 0) { + res = new ArrayList<>(n); + } else { + res = new ArrayList<>(); } - return buffer; - } - default int readBinHelper(ByteBuffer buffer, InputStream inputStream) throws IOException { - int bytesToRead = buffer.remaining(); - byte[] b = new byte[bytesToRead]; - int totalRead = 0; - int thisRead = 0; - while ((totalRead < bytesToRead) && ((thisRead = inputStream.read(b, totalRead, bytesToRead - totalRead)) > 0)) { - totalRead += thisRead; - } - buffer.put(b, 0, totalRead); - return totalRead; - } + for (int i = 0; i < n || n < 0; i++) { + String s = readOneLineWithPushBack(res, i); + final int remainingLineCount = n >= 0 ? n - i : n; + if (s == null) { + if (i >= res.size() || res.get(i) == null) { + // no more push back value - default String readCharHelper(int nchars, InputStream in, @SuppressWarnings("unused") boolean useBytes) throws IOException { - byte[] bytes = new byte[nchars]; - in.read(bytes); - int j = 0; - for (; j < bytes.length; j++) { - // strings end at 0 - if (bytes[j] == 0) { + String[] resInternal = readLinesInternal(remainingLineCount, warn, skipNul); + res.addAll(Arrays.asList(resInternal)); + pushBack = null; + break; + } + // else res[i] has been filled - move to trying to fill the next one + } else { + // reached the last push back value without reaching and of line + assert pushBack.size() == 0; + String[] resInternal = readLinesInternal(remainingLineCount, warn, skipNul); + res.addAll(i, Arrays.asList(resInternal)); + if (res.get(i) != null) { + res.set(i, s + res.get(i)); + } else { + res.set(i, s); + } + pushBack = null; break; } } - return new String(bytes, 0, j); - } - } - - private static byte[] checkBuffer(byte[] buffer, int n) { - if (n > buffer.length - 1) { - byte[] newBuffer = new byte[buffer.length + buffer.length / 2]; - System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); - return newBuffer; - } else { - return buffer; - } - } - - abstract static class DelegateRConnection extends RConnection { - protected BaseRConnection base; - - DelegateRConnection(BaseRConnection base) { - this.base = base; - } - - @Override - public int getDescriptor() { - return base.getDescriptor(); + return res.toArray(new String[res.size()]); } @Override - public boolean isTextMode() { - return base.isTextMode(); + public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { + if (pushBack == null) { + return readLinesInternal(n, warn, skipNul); + } else if (pushBack.size() == 0) { + pushBack = null; + return readLinesInternal(n, warn, skipNul); + } else { + return readLinesWithPushBack(n, warn, skipNul); + } } + /** + * Pushes lines back to the connection. + */ @Override - public boolean isOpen() { - return base.isOpen(); + @TruffleBoundary + public final void pushBack(RAbstractStringVector lines, boolean addNewLine) { + if (pushBack == null) { + pushBack = new LinkedList<>(); + } + for (int i = 0; i < lines.getLength(); i++) { + String newLine = lines.getDataAt(i); + if (addNewLine) { + newLine = newLine + '\n'; + } + pushBack.addFirst(newLine); + } } - @Override - public RConnection forceOpen(String modeString) throws IOException { - return base.forceOpen(modeString); + /** + * Return the length of the push back. + */ + @TruffleBoundary + public final int pushBackLength() { + return pushBack == null ? 0 : pushBack.size(); } - @Override - public boolean isSeekable() { - return false; + /** + * Clears the pushback. + */ + @TruffleBoundary + public final void pushBackClear() { + pushBack = null; } + /** + * Support for {@code seek} Internal. Also clears push back lines. + */ @Override public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { - throw RError.error(RError.SHOW_CALLER2, RError.Message.UNSEEKABLE_CONNECTION); + if (isSeekable()) { + // discard any push back strings + pushBackClear(); + } + // Do not throw error at this position, since the error messages varies depending on the + // connection. + return seekInternal(offset, seekMode, seekRWMode); } - } - abstract static class DelegateReadRConnection extends DelegateRConnection { - protected DelegateReadRConnection(BaseRConnection base) { - super(base); + /** + * Returns {@code true} iff the last read operation was blocked or there is unflushed + * output. + */ + public boolean isIncomplete() { + return incomplete; } - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); + protected void setIncomplete(boolean b) { + this.incomplete = b; } - @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); - } + public final RAbstractIntVector asVector() { + String[] classes = new String[]{ConnectionSupport.getBaseConnection(this).getConnectionClass().getPrintName(), "connection"}; - @Override - public void writeString(String s, boolean nl) throws IOException { - throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); - } + RAbstractIntVector result = RDataFactory.createIntVector(new int[]{getDescriptor()}, true); - @Override - public void writeBin(ByteBuffer buffer) throws IOException { - throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); + RStringVector classVector = RDataFactory.createStringVector(classes, RDataFactory.COMPLETE_VECTOR); + // it's important to put "this" into the externalptr, so that it doesn't get collected + RExternalPtr connectionId = RDataFactory.createExternalPtr(null, this, RDataFactory.createSymbol("connection"), RNull.instance); + DynamicObject attrs = RAttributesLayout.createClassWithConnId(classVector, connectionId); + result.initAttributes(attrs); + return result; } + } - @Override - public int getc() throws IOException { - return getInputStream().read(); + public static BaseRConnection getBaseConnection(RConnection conn) { + if (conn instanceof BaseRConnection) { + return (BaseRConnection) conn; + } else if (conn instanceof DelegateReadRConnection) { + return ((DelegateRConnection) conn).base; + } else { + throw RInternalError.shouldNotReachHere(); } + } - @Override - public void flush() { - // nothing to do - } + abstract static class BasePathRConnection extends BaseRConnection { + /** The path of the actual file to open. */ + protected final String path; - @Override - public OutputStream getOutputStream() { - throw RInternalError.shouldNotReachHere(); - } + /** The description used in the call (required summary output). */ + protected final String description; - @Override - public boolean canRead() { - return true; + protected BasePathRConnection(String description, String path, ConnectionClass connectionClass, String modeString, String encoding) throws IOException { + this(description, path, connectionClass, modeString, AbstractOpenMode.Read, encoding); } - @Override - public boolean canWrite() { - return false; + protected BasePathRConnection(String description, String path, ConnectionClass connectionClass, String modeString, boolean blocking, String encoding) throws IOException { + this(description, path, connectionClass, modeString, AbstractOpenMode.Read, blocking, encoding); } - } - abstract static class DelegateWriteRConnection extends DelegateRConnection { - protected DelegateWriteRConnection(BaseRConnection base) { - super(base); + protected BasePathRConnection(String description, String path, ConnectionClass connectionClass, String modeString, AbstractOpenMode defaultLazyOpenMode, String encoding) throws IOException { + super(connectionClass, modeString, defaultLazyOpenMode, encoding); + this.path = Utils.tildeExpand(path); + this.description = description; } - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + protected BasePathRConnection(String description, String path, ConnectionClass connectionClass, String modeString, AbstractOpenMode defaultLazyOpenMode, boolean blocking, String encoding) + throws IOException { + super(connectionClass, modeString, defaultLazyOpenMode, blocking, encoding); + this.path = Utils.tildeExpand(path); + this.description = description; } @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + public String getSummaryDescription() { + // Use 'description' and not 'path' since this may be different, e.g., on temp files. + return description; } + } - @Override - public int readBin(ByteBuffer buffer) throws IOException { - throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); - } + public static ByteChannel newChannel(InputStream in) { + final ReadableByteChannel newChannel = Channels.newChannel(in); + return new ByteChannel() { - @Override - public byte[] readBinChars() throws IOException { - throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); - } + @Override + public int read(ByteBuffer dst) throws IOException { + return newChannel.read(dst); + } - @Override - public int getc() throws IOException { - throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); - } + @Override + public boolean isOpen() { + return newChannel.isOpen(); + } - @Override - public InputStream getInputStream() { - throw RInternalError.shouldNotReachHere(); - } + @Override + public void close() throws IOException { + newChannel.close(); - @Override - public boolean canRead() { - return false; - } + } - @Override - public boolean canWrite() { - return true; - } + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException("This channel is read-only."); + } + }; } - abstract static class DelegateReadWriteRConnection extends DelegateRConnection { - protected DelegateReadWriteRConnection(BaseRConnection base) { - super(base); - } + public static ByteChannel newChannel(OutputStream out) { + final WritableByteChannel newChannel = Channels.newChannel(out); + return new ByteChannel() { - @Override - public boolean canRead() { - return true; - } - - @Override - public boolean canWrite() { - return true; - } + @Override + public int read(ByteBuffer dst) throws IOException { + throw new IOException("This channel is write-only."); + } - @Override - public int getc() throws IOException { - return getInputStream().read(); - } - } + @Override + public boolean isOpen() { + return newChannel.isOpen(); + } - abstract static class BasePathRConnection extends BaseRConnection { - protected final String path; + @Override + public void close() throws IOException { + newChannel.close(); - protected BasePathRConnection(String path, ConnectionClass connectionClass, String modeString) throws IOException { - this(path, connectionClass, modeString, AbstractOpenMode.Read); - } + } - protected BasePathRConnection(String path, ConnectionClass connectionClass, String modeString, AbstractOpenMode defaultLazyOpenMode) throws IOException { - super(connectionClass, modeString, defaultLazyOpenMode); - this.path = Utils.tildeExpand(path); - } + @Override + public int write(ByteBuffer src) throws IOException { + return newChannel.write(src); + } + }; + } - @Override - public String getSummaryDescription() { - return path; + /** + * Converts between character set names of iconv and Java. + * + * @param encoding The iconv name of the character set to use. + * @return The Java name of the character set to use. + */ + public static String convertEncodingName(String encoding) { + if (encoding == null || encoding.isEmpty() || "native.enc".equals(encoding)) { + return Charset.defaultCharset().name(); } + return encoding; } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateRConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateRConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..0139c2e9a71b7867f954a047e6b0f8acc87d21a4 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateRConnection.java @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.runtime.conn; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.ArrayList; +import java.util.Objects; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RInternalError; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; +import com.sun.istack.internal.NotNull; + +import sun.nio.cs.StreamDecoder; + +/** + * Actually performs the I/O operations for a connections.<br> + * <p> + * A delegate connection is called from its base connection and implements the actual I/O + * operations. + * </p> + */ +abstract class DelegateRConnection implements RConnection { + protected final BaseRConnection base; + private StreamDecoder decoder; + + DelegateRConnection(BaseRConnection base) { + this.base = Objects.requireNonNull(base); + } + + @Override + public int getDescriptor() { + return base.getDescriptor(); + } + + @Override + public boolean isTextMode() { + return base.isTextMode(); + } + + @Override + public boolean isOpen() { + return base.isOpen(); + } + + @Override + public RConnection forceOpen(String modeString) throws IOException { + return base.forceOpen(modeString); + } + + @Override + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + if (!isSeekable()) { + throw RError.error(RError.SHOW_CALLER, RError.Message.NOT_ENABLED_FOR_THIS_CONN, "seek"); + } + throw RInternalError.shouldNotReachHere("seek has not been implemented for this connection"); + } + + /** + * {@code readLines} from the connection. It would be convenient to use a {@link BufferedReader} + * but mixing binary and text operations, which is a requirement, would then be difficult. + * + * @param warn Specifies if warnings should be output. + * @param skipNul Specifies if the null character should be ignored. + */ + @Override + @TruffleBoundary + public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { + base.setIncomplete(false); + ArrayList<String> lines = new ArrayList<>(); + int totalRead = 0; + byte[] buffer = new byte[64]; + int pushBack = 0; + boolean nullRead = false; + while (true) { + int ch; + if (pushBack != 0) { + ch = pushBack; + pushBack = 0; + } else { + ch = getc(); + } + boolean lineEnd = false; + if (ch < 0) { + if (totalRead > 0) { + /* + * GnuR says if non-blocking and in text mode, silently push back incomplete + * lines, otherwise keep data and output warning. + */ + final String incompleteFinalLine = new String(buffer, 0, totalRead, base.getEncoding()); + if (!base.isBlocking() && base.isTextMode()) { + base.pushBack(RDataFactory.createStringVector(incompleteFinalLine), false); + base.setIncomplete(true); + } else { + lines.add(incompleteFinalLine); + if (warn) { + RError.warning(RError.SHOW_CALLER, RError.Message.INCOMPLETE_FINAL_LINE, base.getSummaryDescription()); + } + } + } + break; + } + if (ch == '\n') { + lineEnd = true; + } else if (ch == '\r') { + lineEnd = true; + ch = getc(); + if (ch == '\n') { + // swallow the trailing lf + } else { + pushBack = ch; + } + } else if (ch == 0) { + nullRead = true; + if (warn && !skipNul) { + RError.warning(RError.SHOW_CALLER, RError.Message.LINE_CONTAINS_EMBEDDED_NULLS, lines.size() + 1); + } + } + if (lineEnd) { + lines.add(new String(buffer, 0, totalRead, base.getEncoding())); + if (n > 0 && lines.size() == n) { + break; + } + totalRead = 0; + nullRead = false; + } else { + if (!nullRead) { + buffer = DelegateRConnection.checkBuffer(buffer, totalRead); + buffer[totalRead++] = (byte) (ch & 0xFF); + } + if (skipNul) { + nullRead = false; + } + } + } + String[] result = new String[lines.size()]; + lines.toArray(result); + return result; + } + + /** + * Writes a string to a channel. + * + * @param out the channel + * @param s The actual string to write. + * @param nl Indicates if a line separator should be appended. + * @param encoding The encoding to use for writing. + * @return {@code true} if an incomplete line was written; {@code false} otherwise + * @throws IOException + */ + public static boolean writeStringHelper(WritableByteChannel out, String s, boolean nl, Charset encoding) throws IOException { + boolean incomplete; + final byte[] bytes = s.getBytes(encoding); + final byte[] lineSepBytes = nl ? System.lineSeparator().getBytes(encoding) : null; + + ByteBuffer buf = ByteBuffer.allocate(bytes.length + (nl ? lineSepBytes.length : 0)); + buf.put(bytes); + if (nl) { + buf.put(lineSepBytes); + incomplete = false; + } else { + incomplete = !s.contains("\n"); + } + + buf.rewind(); + out.write(buf); + return incomplete; + } + + /** + * Writes characters in binary mode (without any re-encoding) to the provided channel. + * + * @param channel The writable byte channel to write to (must not be {@code null}). + * @param s The character string to write (must not be {@code null}). + * @param pad The number of null characters to append to the characters. + * @param eos The end-of-string terminator (may be {@code null}). + * @throws IOException + */ + public static void writeCharHelper(@NotNull WritableByteChannel channel, @NotNull String s, int pad, String eos) throws IOException { + + final byte[] bytes = s.getBytes(); + final byte[] eosBytes = eos != null ? eos.getBytes() : null; + + final int bufLen = bytes.length + (pad > 0 ? pad : 0) + (eos != null ? eosBytes.length + 1 : 0); + assert bufLen >= s.length(); + ByteBuffer buf = ByteBuffer.allocate(bufLen); + buf.put(bytes); + if (pad > 0) { + for (int i = 0; i < pad; i++) { + buf.put((byte) 0); + } + } + if (eos != null) { + if (eos.length() > 0) { + buf.put(eos.getBytes()); + } + // function writeChar is defined to append the null character if eos != null + buf.put((byte) 0); + } + buf.rewind(); + channel.write(buf); + } + + /** + * Reads null-terminated character strings from a {@link ReadableByteChannel}. + */ + public static byte[] readBinCharsHelper(ReadableByteChannel channel) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(1); + int numRead = channel.read(buf); + if (numRead <= 0) { + return null; + } + int totalRead = 0; + byte[] buffer = new byte[64]; + while (true) { + buffer = DelegateRConnection.checkBuffer(buffer, totalRead); + buffer[totalRead++] = (byte) (buf.get() & 0xFF); + if (numRead == 0) { + break; + } + buf.clear(); + numRead = channel.read(buf); + } + return buffer; + } + + /** + * Reads a specified amount of characters. + * + * @param nchars Number of characters to read. + * @param in The encoded byte stream. + * @return The read string. + * @throws IOException + */ + public static String readCharHelper(int nchars, Reader in) throws IOException { + char[] chars = new char[nchars]; + in.read(chars); + int j = 0; + for (; j < chars.length; j++) { + // strings end at 0 + if (chars[j] == 0) { + break; + } + } + + return new String(chars, 0, j); + } + + /** + * Reads a specified number of single-byte characters.<br> + * <p> + * This method is meant to be used if R's function {@code readChar} is called with parameter + * {@code useBytes=TRUE}. + * </p> + * + * @param nchars The number of single-byte characters to read. + * @param channel The channel to read from (must not be {@code null}). + * @throws IOException + */ + public static String readCharHelper(int nchars, ReadableByteChannel channel) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(nchars); + channel.read(buf); + int j = 0; + for (; j < buf.position(); j++) { + // strings end at 0 + if (buf.get(j) == 0) { + break; + } + } + + return new String(buf.array(), 0, j); + } + + /** + * Reads a specified number of characters (not bytes).<br> + * <p> + * This method is meant to be used if R's function {@code readChar} is called with parameter + * {@code useBytes=FALSE}. + * </p> + * + * @param nchars The number of characters to read. + * @param decoder The stream decoder to use (must not be {@code null}). + * @throws IOException + */ + public static String readCharHelper(int nchars, StreamDecoder decoder) throws IOException { + // we need a decoder + char[] chars = new char[nchars]; + decoder.read(chars); + int j = 0; + for (; j < chars.length; j++) { + // strings end at 0 + if (chars[j] == 0) { + break; + } + } + return new String(chars, 0, j); + } + + /** + * Implements standard seeking behavior. + */ + public static long seek(SeekableByteChannel channel, long offset, SeekMode seekMode, @SuppressWarnings("unused") SeekRWMode seekRWMode) throws IOException { + long position = channel.position(); + switch (seekMode) { + case ENQUIRE: + break; + case CURRENT: + if (offset != 0) { + channel.position(position + offset); + } + break; + case START: + channel.position(offset); + break; + case END: + throw RInternalError.unimplemented(); + + } + return position; + } + + /** + * Enlarges the buffer if necessary. + */ + private static byte[] checkBuffer(byte[] buffer, int n) { + if (n > buffer.length - 1) { + byte[] newBuffer = new byte[buffer.length + buffer.length / 2]; + System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); + return newBuffer; + } else { + return buffer; + } + } + + public static boolean writeLinesHelper(WritableByteChannel out, RAbstractStringVector lines, String sep, Charset encoding) throws IOException { + boolean incomplete = false; + for (int i = 0; i < lines.getLength(); i++) { + final String line = lines.getDataAt(i); + incomplete = DelegateRConnection.writeStringHelper(out, line, false, encoding); + incomplete = DelegateRConnection.writeStringHelper(out, sep, false, encoding) || incomplete; + } + return incomplete; + } + + @Override + public void pushBack(RAbstractStringVector lines, boolean addNewLine) { + throw RInternalError.shouldNotReachHere(); + } + + /** + * Creates the stream decoder on demand and returns it. + */ + protected StreamDecoder getDecoder() throws IOException { + if (decoder == null) { + CharsetDecoder charsetEncoder = base.getEncoding().newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); + decoder = StreamDecoder.forDecoder(getChannel(), charsetEncoder, -1); + } + return decoder; + } +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateReadRConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateReadRConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..70a2bac8df5e6cc84b12f1b876f627b4d56a0b26 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateReadRConnection.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.runtime.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; + +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RInternalError; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; + +public abstract class DelegateReadRConnection extends DelegateRConnection { + + private final ByteBuffer tmp = ByteBuffer.allocate(1); + + protected DelegateReadRConnection(BaseRConnection base) { + super(base); + } + + @Override + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { + throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); + } + + @Override + public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { + throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); + } + + @Override + public void writeString(String s, boolean nl) throws IOException { + throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); + } + + @Override + public void writeBin(ByteBuffer buffer) throws IOException { + throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); + } + + @Override + public int getc() throws IOException { + if (isTextMode()) { + return getDecoder().read(); + } else { + tmp.clear(); + int nread = getChannel().read(tmp); + tmp.rewind(); + return nread > 0 ? tmp.get() : -1; + } + } + + @Override + public String readChar(int nchars, boolean useBytes) throws IOException { + if (useBytes) { + return DelegateRConnection.readCharHelper(nchars, getChannel()); + } else { + return DelegateRConnection.readCharHelper(nchars, getDecoder()); + } + } + + @Override + public int readBin(ByteBuffer buffer) throws IOException { + return getChannel().read(buffer); + } + + @Override + public byte[] readBinChars() throws IOException { + return DelegateRConnection.readBinCharsHelper(getChannel()); + } + + @Override + public void flush() { + // nothing to do when reading + } + + @Override + public OutputStream getOutputStream() { + throw RInternalError.shouldNotReachHere(); + } + + @Override + public boolean canRead() { + return true; + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public InputStream getInputStream() throws IOException { + return Channels.newInputStream(getChannel()); + } + + @Override + public void close() throws IOException { + getChannel().close(); + } + + @Override + public void closeAndDestroy() throws IOException { + base.closed = true; + close(); + } + +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateReadWriteRConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateReadWriteRConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..011cf49d1b9256813e2f4151b61cf8565dd1f695 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateReadWriteRConnection.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.runtime.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; + +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; + +abstract class DelegateReadWriteRConnection extends DelegateRConnection { + + private final ByteBuffer tmp = ByteBuffer.allocate(1); + + protected DelegateReadWriteRConnection(BaseRConnection base) { + super(base); + } + + @Override + public boolean canRead() { + return true; + } + + @Override + public boolean canWrite() { + return true; + } + + @Override + public int getc() throws IOException { + tmp.clear(); + int nread = getChannel().read(tmp); + tmp.rewind(); + return nread > 0 ? tmp.get() : -1; + } + + @Override + public String readChar(int nchars, boolean useBytes) throws IOException { + if (useBytes) { + return DelegateRConnection.readCharHelper(nchars, getChannel()); + } else { + return DelegateRConnection.readCharHelper(nchars, getDecoder()); + } + } + + @Override + public int readBin(ByteBuffer buffer) throws IOException { + return getChannel().read(buffer); + } + + @Override + public byte[] readBinChars() throws IOException { + return DelegateRConnection.readBinCharsHelper(getChannel()); + } + + @Override + public void flush() { + // nothing to do for channels + } + + @Override + public OutputStream getOutputStream() throws IOException { + return Channels.newOutputStream(getChannel()); + } + + @Override + public InputStream getInputStream() throws IOException { + return Channels.newInputStream(getChannel()); + } + + @Override + public void close() throws IOException { + getChannel().close(); + } + + @Override + public void closeAndDestroy() throws IOException { + base.closed = true; + close(); + } + + @Override + public void writeBin(ByteBuffer buffer) throws IOException { + getChannel().write(buffer); + } + + @Override + public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { + DelegateRConnection.writeCharHelper(getChannel(), s, pad, eos); + } + + @Override + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { + boolean incomplete = DelegateRConnection.writeLinesHelper(getChannel(), lines, sep, base.getEncoding()); + base.setIncomplete(incomplete); + } + + @Override + public void writeString(String s, boolean nl) throws IOException { + DelegateRConnection.writeStringHelper(getChannel(), s, nl, base.getEncoding()); + } + +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateWriteRConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateWriteRConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..4d4520d88cfea5b2ce808e579e45c8f4991fe4b5 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/DelegateWriteRConnection.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.runtime.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; + +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RInternalError; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; + +abstract class DelegateWriteRConnection extends DelegateRConnection { + + protected DelegateWriteRConnection(BaseRConnection base) { + super(base); + } + + @Override + public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { + throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + } + + @Override + public String readChar(int nchars, boolean useBytes) throws IOException { + throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + } + + @Override + public int readBin(ByteBuffer buffer) throws IOException { + throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + } + + @Override + public byte[] readBinChars() throws IOException { + throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + } + + @Override + public int getc() throws IOException { + throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message); + } + + @Override + public InputStream getInputStream() { + throw RInternalError.shouldNotReachHere(); + } + + @Override + public boolean canRead() { + return false; + } + + @Override + public boolean canWrite() { + return true; + } + + @Override + public void closeAndDestroy() throws IOException { + base.closed = true; + close(); + } + + @Override + public void flush() throws IOException { + // channels don't need any flushing + } + + @Override + public void close() throws IOException { + flush(); + getChannel().close(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return Channels.newOutputStream(getChannel()); + } + + @Override + public void writeBin(ByteBuffer buffer) throws IOException { + getChannel().write(buffer); + } + + @Override + public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { + DelegateRConnection.writeCharHelper(getChannel(), s, pad, eos); + } + + @Override + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { + boolean incomplete = DelegateRConnection.writeLinesHelper(getChannel(), lines, sep, base.getEncoding()); + base.setIncomplete(incomplete); + } + + @Override + public void writeString(String s, boolean nl) throws IOException { + DelegateRConnection.writeStringHelper(getChannel(), s, nl, base.getEncoding()); + } + +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FifoConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FifoConnections.java new file mode 100644 index 0000000000000000000000000000000000000000..df63d1fc42ab27e999a08ff4488191dfb5be44f9 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FifoConnections.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.runtime.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.lang.ProcessBuilder.Redirect; +import java.nio.channels.ByteChannel; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; + +import com.oracle.truffle.r.runtime.ProcessOutputManager; +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; + +public class FifoConnections { + + public static class FifoRConnection extends BaseRConnection { + + private final String path; + + public FifoRConnection(String path, String open, boolean blocking, String encoding) throws IOException { + super(ConnectionClass.FIFO, open, AbstractOpenMode.Read, blocking, encoding); + this.path = path; + openNonLazyConnection(); + } + + @Override + protected void createDelegateConnection() throws IOException { + final DelegateRConnection delegate; + if (isBlocking()) { + delegate = createBlockingDelegate(); + } else { + + delegate = createNonBlockingDelegate(); + } + setDelegate(delegate); + } + + private DelegateRConnection createBlockingDelegate() throws IOException { + DelegateRConnection delegate = null; + switch (getOpenMode().abstractOpenMode) { + case Read: + case ReadBinary: + delegate = new FifoReadRConnection(this, path); + break; + case Write: + case WriteBinary: + delegate = new FifoWriteConnection(this, path); + break; + case ReadAppend: + case ReadWrite: + case ReadWriteBinary: + case ReadWriteTrunc: + case ReadWriteTruncBinary: + delegate = new FifoReadWriteConnection(this, path); + break; + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + getOpenMode()); + } + return delegate; + } + + private DelegateRConnection createNonBlockingDelegate() throws IOException { + DelegateRConnection delegate = null; + switch (getOpenMode().abstractOpenMode) { + case Read: + case ReadBinary: + delegate = new FifoReadNonBlockingRConnection(this, path); + break; + case Write: + case WriteBinary: + delegate = new FifoWriteNonBlockingRConnection(this, path); + break; + case ReadAppend: + case ReadWrite: + case ReadWriteBinary: + case ReadWriteTrunc: + case ReadWriteTruncBinary: + delegate = new FifoReadWriteNonBlockingRConnection(this, path); + break; + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + getOpenMode()); + } + return delegate; + } + + @Override + public String getSummaryDescription() { + return path; + } + } + + static class FifoReadRConnection extends DelegateReadRConnection { + private final FileChannel channel; + + protected FifoReadRConnection(BaseRConnection base, String path) throws IOException { + super(base); + channel = FileChannel.open(Paths.get(path), StandardOpenOption.READ); + } + + @Override + public ByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + } + + private static class FifoWriteConnection extends DelegateWriteRConnection { + private final RandomAccessFile raf; + + FifoWriteConnection(BaseRConnection base, String path) throws IOException { + super(base); + this.raf = createAndOpenFifo(path, "rw"); + } + + @Override + public SeekableByteChannel getChannel() { + return raf.getChannel(); + } + + @Override + public void close() throws IOException { + raf.close(); + } + + @Override + public boolean isSeekable() { + return false; + } + } + + private static class FifoReadWriteConnection extends DelegateReadWriteRConnection { + + private final RandomAccessFile raf; + + protected FifoReadWriteConnection(BaseRConnection base, String path) throws IOException { + super(base); + this.raf = createAndOpenFifo(path, "rw"); + } + + @Override + public boolean isSeekable() { + return false; + } + + @Override + public ByteChannel getChannel() { + return raf.getChannel(); + } + + } + + static class FifoReadNonBlockingRConnection extends DelegateReadRConnection { + private final FileChannel channel; + + protected FifoReadNonBlockingRConnection(BaseRConnection base, String path) throws IOException { + super(base); + channel = FileChannel.open(Paths.get(path), StandardOpenOption.READ); + } + + @Override + public SeekableByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + } + + private static class FifoWriteNonBlockingRConnection extends DelegateWriteRConnection { + private final FileChannel channel; + + FifoWriteNonBlockingRConnection(BaseRConnection base, String path) throws IOException { + super(base); + channel = createAndOpenNonBlockingFifo(path, StandardOpenOption.READ, StandardOpenOption.WRITE); + } + + @Override + public SeekableByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + } + + private static class FifoReadWriteNonBlockingRConnection extends DelegateReadWriteRConnection { + private final FileChannel channel; + + FifoReadWriteNonBlockingRConnection(BaseRConnection base, String path) throws IOException { + super(base); + channel = createAndOpenNonBlockingFifo(path, StandardOpenOption.WRITE, StandardOpenOption.READ); + } + + @Override + public SeekableByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + + } + + private static final String MKFIFO_ERROR_FILE_EXISTS = "File exists"; + + private static RandomAccessFile createAndOpenFifo(String path, String mode) throws IOException { + if (!Files.exists(Paths.get(path))) { + // try to create fifo on demand + createNamedPipe(path); + } + return new RandomAccessFile(path, mode); + } + + private static FileChannel createAndOpenNonBlockingFifo(String path, OpenOption... openOptions) throws IOException { + if (!Files.exists(Paths.get(path))) { + // try to create fifo on demand + createNamedPipe(path); + } + return FileChannel.open(Paths.get(path), openOptions); + } + + /** + * Creates a named pipe (FIFO) by invoking {@code mkfifo} in a shell. + * + * <b>NOTE:</b> This is a Linux-specific operation and won't work on other OSes. + * + * @param path The path to the named pipe. + * @throws IOException + */ + private static void createNamedPipe(String path) throws IOException { + + String[] command = new String[]{"mkfifo", path}; + ProcessBuilder pb = new ProcessBuilder(command); + pb.redirectError(Redirect.INHERIT); + Process p = pb.start(); + InputStream is = p.getInputStream(); + ProcessOutputManager.OutputThreadVariable readThread = new ProcessOutputManager.OutputThreadVariable(command[0], is); + readThread.start(); + try { + int rc = p.waitFor(); + if (rc == 0) { + readThread.join(); + String msg = new String(Arrays.copyOf(readThread.getData(), readThread.getTotalRead())); + + // if the message is not empty and it is not the "file exists" error, then throw + // error + if (!msg.isEmpty() && !msg.endsWith(MKFIFO_ERROR_FILE_EXISTS)) { + throw new IOException(msg); + } + } + } catch (InterruptedException ex) { + // fall through + } + } + +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java index 77c0e2875edf4e45f279d1eea5742f279efdd91d..a72e817ec18f761e35c1fc294f02669dc40240b2 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java @@ -22,9 +22,8 @@ */ package com.oracle.truffle.r.runtime.conn; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -32,34 +31,44 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.FileChannel; +import java.nio.file.OpenOption; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import org.tukaani.xz.LZMA2Options; +import org.tukaani.xz.XZ; import org.tukaani.xz.XZInputStream; +import org.tukaani.xz.XZOutputStream; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.r.runtime.RCompression; +import com.oracle.truffle.r.runtime.RCompression.Type; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.TempPathName; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BasePathRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadWriteRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateWriteRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.OpenMode; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ReadWriteHelper; import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; public class FileConnections { + public static final int GZIP_BUFFER_SIZE = (2 << 20); + /** * Base class for all modes of file connections. - * */ public static class FileRConnection extends BasePathRConnection { + private final boolean raw; - public FileRConnection(String path, String modeString) throws IOException { - super(checkTemp(path), ConnectionClass.File, modeString); + public FileRConnection(String description, String path, String modeString, boolean blocking, String encoding, boolean raw) throws IOException { + super(description, checkTemp(path), ConnectionClass.File, modeString, blocking, encoding); + this.raw = raw; openNonLazyConnection(); } @@ -73,278 +82,251 @@ public class FileConnections { @Override protected void createDelegateConnection() throws IOException { - DelegateRConnection delegate = null; - switch (getOpenMode().abstractOpenMode) { - case Read: - delegate = new FileReadTextRConnection(this); - break; - case Write: - delegate = new FileWriteTextRConnection(this, false); - break; - case Append: - delegate = new FileWriteTextRConnection(this, true); - break; - case ReadBinary: - delegate = new FileReadBinaryRConnection(this); - break; - case WriteBinary: - delegate = new FileWriteBinaryConnection(this, false); - break; - case ReadWriteTrunc: - case ReadWriteTruncBinary: - delegate = new FileReadWriteConnection(this); - break; - default: - throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + getOpenMode()); - } + + DelegateRConnection delegate = FileConnections.createDelegateConnection(this, RCompression.Type.NONE, raw); setDelegate(delegate); } } - static class FileReadTextRConnection extends DelegateReadRConnection implements ReadWriteHelper { - private InputStream inputStream; - - FileReadTextRConnection(BasePathRConnection base) throws IOException { - super(base); - // can be compressed - check for it - RCompression.Type cType = RCompression.getCompressionType(base.path); - switch (cType) { - case NONE: - inputStream = new BufferedInputStream(new FileInputStream(base.path)); - break; - case GZIP: - inputStream = new GZIPInputStream(new FileInputStream(base.path), CompressedConnections.GZIP_BUFFER_SIZE); - break; - case BZIP2: - // no in Java support, so go via byte array - byte[] bzipUdata = RCompression.bzipUncompressFromFile(base.path); - inputStream = new ByteArrayInputStream(bzipUdata); - break; - case XZ: - inputStream = new XZInputStream(new FileInputStream(base.path)); - break; - default: - throw RError.nyi(RError.SHOW_CALLER2, "compression type: " + cType.name()); - } + /** + * Base class for all modes of gzfile/bzfile/xzfile connections. N.B. In GNU R these can read + * gzip, bzip, lzma and uncompressed files, and this has to be implemented by reading the first + * few bytes of the file and detecting the type of the file. + */ + public static class CompressedRConnection extends BasePathRConnection { + private final RCompression.Type cType; + @SuppressWarnings("unused") private final int compression; // TODO + + public CompressedRConnection(String path, String modeString, Type cType, String encoding, int compression) throws IOException { + super(path, path, mapConnectionClass(cType), modeString, AbstractOpenMode.ReadBinary, encoding); + this.cType = cType; + this.compression = compression; + openNonLazyConnection(); } @Override - public int readBin(ByteBuffer buffer) throws IOException { - throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_READ_BINARY_CONNECTION); - } + protected void createDelegateConnection() throws IOException { + setDelegate(FileConnections.createDelegateConnection(this, cType, false)); - @Override - public byte[] readBinChars() throws IOException { - throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_READ_BINARY_CONNECTION); } - @TruffleBoundary - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - return readLinesHelper(inputStream, n, warn, skipNul); - } + // @Override + /** + * GnuR behavior for lazy connections is odd, e.g. gzfile returns "text", even though the + * default mode is "rb". + */ + // public boolean isTextMode() { + // } + } - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, inputStream, useBytes); - } + private static DelegateRConnection createUncompressedDelegateConnection(BasePathRConnection base) + throws IOException { + + DelegateRConnection delegate = null; + switch (base.getOpenMode().abstractOpenMode) { + case Read: + delegate = new FileReadTextRConnection(base); + break; + case ReadBinary: + delegate = new FileReadBinaryRConnection(base); + break; + case Write: + delegate = new FileWriteTextRConnection(base, false); + break; + case Append: + delegate = new FileWriteTextRConnection(base, true); + break; + case WriteBinary: + delegate = new FileWriteBinaryConnection(base, false); + break; + case ReadWriteTrunc: + case ReadWriteTruncBinary: + delegate = new FileReadWriteConnection(base); + break; + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + base.getOpenMode()); + } + return delegate; + } - @Override - public InputStream getInputStream() throws IOException { - return inputStream; + private static DelegateRConnection createGZIPDelegateConnection(BasePathRConnection base) throws IOException { + + switch (base.getOpenMode().abstractOpenMode) { + case Read: + case ReadBinary: + return new CompressedInputRConnection(base, new GZIPInputStream(new FileInputStream(base.path), GZIP_BUFFER_SIZE)); + case Append: + case AppendBinary: + return new CompressedOutputRConnection(base, new GZIPOutputStream(new FileOutputStream(base.path, true), GZIP_BUFFER_SIZE), true); + case Write: + case WriteBinary: + return new CompressedOutputRConnection(base, new GZIPOutputStream(new FileOutputStream(base.path, false), GZIP_BUFFER_SIZE), true); + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + base.getOpenMode()); } + } - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); + private static DelegateRConnection createXZDelegateConnection(BasePathRConnection base) throws IOException { + + switch (base.getOpenMode().abstractOpenMode) { + case Read: + case ReadBinary: + return new CompressedInputRConnection(base, new XZInputStream(new FileInputStream(base.path))); + case Append: + case AppendBinary: + return new CompressedOutputRConnection(base, new XZOutputStream(new FileOutputStream(base.path, true), new LZMA2Options(), XZ.CHECK_CRC32), false); + case Write: + case WriteBinary: + return new CompressedOutputRConnection(base, new XZOutputStream(new FileOutputStream(base.path, false), new LZMA2Options(), XZ.CHECK_CRC32), false); + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + base.getOpenMode()); } + } - @Override - public void close() throws IOException { - inputStream.close(); + private static DelegateRConnection createBZIP2DelegateConnection(BasePathRConnection base) throws IOException { + + switch (base.getOpenMode().abstractOpenMode) { + case Read: + case ReadBinary: + byte[] bzipUdata = RCompression.bzipUncompressFromFile(base.path); + return new ByteStreamCompressedInputRConnection(base, new ByteArrayInputStream(bzipUdata)); + case Append: + case AppendBinary: + return new BZip2OutputRConnection(base, new ByteArrayOutputStream(), true); + case Write: + case WriteBinary: + return new BZip2OutputRConnection(base, new ByteArrayOutputStream(), false); + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + base.getOpenMode()); } } - private static class FileWriteTextRConnection extends DelegateWriteRConnection implements ReadWriteHelper { - private final BufferedOutputStream outputStream; + private static DelegateRConnection createDelegateConnection(BasePathRConnection base, RCompression.Type cType, boolean raw) throws IOException { + AbstractOpenMode openMode = base.getOpenMode().abstractOpenMode; - FileWriteTextRConnection(FileRConnection base, boolean append) throws IOException { - super(base); - outputStream = new BufferedOutputStream(new FileOutputStream(base.path, append)); + /* + * For input, we check the actual compression type as GNU R is permissive about the claimed + * type except 'raw' is true. + */ + final RCompression.Type cTypeActual; + if (!raw && (openMode == AbstractOpenMode.Read || openMode == AbstractOpenMode.ReadBinary)) { + cTypeActual = RCompression.getCompressionType(base.path); + if (cTypeActual != cType) { + base.updateConnectionClass(mapConnectionClass(cTypeActual)); + } + } else { + cTypeActual = cType; } - @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - writeCharHelper(outputStream, s, pad, eos); + switch (cTypeActual) { + case NONE: + return createUncompressedDelegateConnection(base); + case GZIP: + return createGZIPDelegateConnection(base); + case XZ: + return createXZDelegateConnection(base); + case BZIP2: + return createBZIP2DelegateConnection(base); } + throw RInternalError.shouldNotReachHere("unsupported compression type"); + } - @Override - public void writeBin(ByteBuffer buffer) throws IOException { - throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_WRITE_BINARY_CONNECTION); + private static ConnectionClass mapConnectionClass(RCompression.Type cType) { + switch (cType) { + case NONE: + return ConnectionClass.File; + case GZIP: + return ConnectionClass.GZFile; + case BZIP2: + return ConnectionClass.BZFile; + case XZ: + return ConnectionClass.XZFile; + default: + throw RInternalError.shouldNotReachHere(); } + } - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - writeLinesHelper(outputStream, lines, sep); - flush(); - } + static class FileReadBinaryRConnection extends DelegateReadRConnection { - @Override - public void writeString(String s, boolean nl) throws IOException { - writeStringHelper(outputStream, s, nl); - } + private final FileChannel channel; - @Override - public OutputStream getOutputStream() throws IOException { - return outputStream; + FileReadBinaryRConnection(BasePathRConnection base) throws IOException { + super(base); + channel = FileChannel.open(Paths.get(base.path), StandardOpenOption.READ); } @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); + public boolean isSeekable() { + return true; } @Override - public void close() throws IOException { - outputStream.close(); + public ByteChannel getChannel() { + return channel; } - @Override - public void flush() throws IOException { - outputStream.flush(); - } } - static class FileReadBinaryRConnection extends DelegateReadRConnection implements ReadWriteHelper { - private final FileInputStream inputStream; + static class FileReadTextRConnection extends FileReadBinaryRConnection { - FileReadBinaryRConnection(BasePathRConnection base) throws IOException { + FileReadTextRConnection(BasePathRConnection base) throws IOException { super(base); - inputStream = new FileInputStream(base.path); - } - - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, inputStream, useBytes); } @Override public int readBin(ByteBuffer buffer) throws IOException { - return inputStream.getChannel().read(buffer); + throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_READ_BINARY_CONNECTION); } @Override public byte[] readBinChars() throws IOException { - return readBinCharsHelper(inputStream); - } - - @TruffleBoundary - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - return readLinesHelper(inputStream, n, warn, skipNul); - } - - @Override - public InputStream getInputStream() throws IOException { - return inputStream; - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); - } - - @Override - public void close() throws IOException { - inputStream.close(); - } - - @Override - public boolean isSeekable() { - return true; - } - - @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { - long position = inputStream.getChannel().position(); - switch (seekMode) { - case ENQUIRE: - break; - case CURRENT: - if (offset != 0) { - inputStream.getChannel().position(position + offset); - } - break; - case START: - inputStream.getChannel().position(offset); - break; - case END: - throw RInternalError.unimplemented(); - - } - return position; + throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_READ_BINARY_CONNECTION); } } - private static class FileWriteBinaryConnection extends DelegateWriteRConnection implements ReadWriteHelper { - private final FileOutputStream outputStream; + private static class FileWriteTextRConnection extends FileWriteBinaryConnection { - FileWriteBinaryConnection(FileRConnection base, boolean append) throws IOException { - super(base); - outputStream = new FileOutputStream(base.path, append); - } - - @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - writeCharHelper(outputStream, s, pad, eos); + FileWriteTextRConnection(BasePathRConnection base, boolean append) throws IOException { + super(base, append); } @Override public void writeBin(ByteBuffer buffer) throws IOException { - outputStream.getChannel().write(buffer); + throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_WRITE_BINARY_CONNECTION); } + } - @Override - public OutputStream getOutputStream() throws IOException { - return outputStream; - } + private static class FileWriteBinaryConnection extends DelegateWriteRConnection { - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); - } + private final FileChannel channel; - @Override - public void close() throws IOException { - flush(); - outputStream.close(); - } - - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - for (int i = 0; i < lines.getLength(); i++) { - String line = lines.getDataAt(i); - outputStream.write(line.getBytes()); - outputStream.write(sep.getBytes()); + FileWriteBinaryConnection(BasePathRConnection base, boolean append) throws IOException { + super(base); + List<OpenOption> opts = new ArrayList<>(); + opts.add(StandardOpenOption.WRITE); + opts.add(StandardOpenOption.CREATE); + if (append) { + opts.add(StandardOpenOption.APPEND); + } else { + opts.add(StandardOpenOption.TRUNCATE_EXISTING); } + channel = FileChannel.open(Paths.get(base.path), opts.toArray(new OpenOption[opts.size()])); + } @Override - public void writeString(String s, boolean nl) throws IOException { - writeStringHelper(outputStream, s, nl); + public boolean isSeekable() { + return true; } @Override - public void flush() throws IOException { - outputStream.flush(); + public ByteChannel getChannel() { + return channel; } + } - private static class FileReadWriteConnection extends DelegateReadWriteRConnection implements ReadWriteHelper { + private static class FileReadWriteConnection extends DelegateReadWriteRConnection { /* * This is a minimal implementation to support one specific use in package installation. * @@ -356,20 +338,8 @@ public class FileConnections { private long readOffset; private long writeOffset; private SeekRWMode lastMode = SeekRWMode.READ; - private final RAFInputStream inputStream; - - /** - * Allows an {@link RandomAccessFile} to appear to be an {@link InputStream}. - * - */ - private class RAFInputStream extends InputStream { - @Override - public int read() throws IOException { - return FileReadWriteConnection.this.getc(); - } - } - FileReadWriteConnection(FileRConnection base) throws IOException { + FileReadWriteConnection(BasePathRConnection base) throws IOException { super(base); OpenMode openMode = base.getOpenMode(); String rafMode = null; @@ -382,7 +352,6 @@ public class FileConnections { throw RInternalError.shouldNotReachHere(); } raf = new RandomAccessFile(base.path, rafMode); - inputStream = new RAFInputStream(); } @Override @@ -430,25 +399,9 @@ public class FileConnections { } @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { + public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { raf.seek(readOffset); - return readLinesHelper(inputStream, n, warn, skipNul); - } - - @Override - public InputStream getInputStream() throws IOException { - return inputStream; - } - - @Override - public OutputStream getOutputStream() throws IOException { - throw RInternalError.unimplemented(); - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); + return super.readLines(n, warn, skipNul); } @Override @@ -458,9 +411,8 @@ public class FileConnections { @Override public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - // TODO encodings raf.seek(writeOffset); - byte[] sepData = sep.getBytes(); + byte[] sepData = sep.getBytes(base.getEncoding()); for (int i = 0; i < lines.getLength(); i++) { writeString(lines.getDataAt(i), false); raf.write(sepData); @@ -469,41 +421,102 @@ public class FileConnections { lastMode = SeekRWMode.WRITE; } - @Override - public void flush() throws IOException { - } - @Override public void writeString(String s, boolean nl) throws IOException { - raf.write(s.getBytes()); + raf.write(s.getBytes(base.getEncoding())); if (nl) { - raf.writeBytes(System.lineSeparator()); + raf.write(System.lineSeparator().getBytes(base.getEncoding())); } } @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - throw RInternalError.unimplemented(); + public ByteChannel getChannel() { + return raf.getChannel(); + } + + } + + private static class CompressedInputRConnection extends DelegateReadRConnection { + private final ByteChannel channel; + + protected CompressedInputRConnection(BasePathRConnection base, InputStream is) { + super(base); + channel = ConnectionSupport.newChannel(is); } @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - throw RInternalError.unimplemented(); + public ByteChannel getChannel() { + return channel; } @Override - public void writeBin(ByteBuffer buffer) throws IOException { - throw RInternalError.unimplemented(); + public boolean isSeekable() { + return false; + } + } + + private static class ByteStreamCompressedInputRConnection extends CompressedInputRConnection { + ByteStreamCompressedInputRConnection(BasePathRConnection base, ByteArrayInputStream is) { + super(base, is); + } + } + + private static class CompressedOutputRConnection extends DelegateWriteRConnection { + protected ByteChannel channel; + private final boolean seekable; + private long seekPosition = 0L; + + protected CompressedOutputRConnection(BasePathRConnection base, OutputStream os, boolean seekable) { + super(base); + this.seekable = seekable; + this.channel = ConnectionSupport.newChannel(os); } @Override - public int readBin(ByteBuffer buffer) throws IOException { - throw RInternalError.unimplemented(); + public void closeAndDestroy() throws IOException { + base.closed = true; + close(); } @Override - public byte[] readBinChars() throws IOException { - throw RInternalError.unimplemented(); + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + if (seekable) { + // TODO GZIP is basically seekable; however, the output stream does not allow any + // seeking + long oldPos = seekPosition; + seekPosition = offset; + return oldPos; + } + return super.seek(offset, seekMode, seekRWMode); + } + + @Override + public boolean isSeekable() { + return seekable; + } + + @Override + public ByteChannel getChannel() { + return channel; + } + } + + private static class BZip2OutputRConnection extends CompressedOutputRConnection { + private final ByteArrayOutputStream bos; + private final boolean append; + + BZip2OutputRConnection(BasePathRConnection base, ByteArrayOutputStream os, boolean append) { + super(base, os, false); + this.bos = os; + this.append = append; + } + + @Override + public void close() throws IOException { + flush(); + // Now actually do the compression using sub-process + byte[] data = bos.toByteArray(); + RCompression.bzipCompressToFile(data, ((BasePathRConnection) base).path, append); } } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/PipeConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/PipeConnections.java new file mode 100644 index 0000000000000000000000000000000000000000..8aa871b1b29f22629d12022f1a37493da5a01970 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/PipeConnections.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.runtime.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ProcessBuilder.Redirect; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; +import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; + +public class PipeConnections { + + private static Process executeAndJoin(String command) throws IOException { + ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", command); + pb.redirectError(Redirect.INHERIT); + Process p = pb.start(); + try { + p.waitFor(); + } catch (InterruptedException e) { + // TODO not sure how to handle an interrupted exception at this point + } + return p; + } + + public static class PipeRConnection extends BaseRConnection { + + private final String command; + + public PipeRConnection(String command, String open, String encoding) throws IOException { + super(ConnectionClass.FIFO, open, AbstractOpenMode.Read, encoding); + this.command = command; + openNonLazyConnection(); + } + + @Override + protected void createDelegateConnection() throws IOException { + final DelegateRConnection delegate; + switch (getOpenMode().abstractOpenMode) { + case Read: + case ReadBinary: + delegate = new PipeReadRConnection(this, command); + break; + case Write: + case WriteBinary: + delegate = new PipeWriteConnection(this, command); + break; + case ReadAppend: + case ReadWrite: + case ReadWriteBinary: + case ReadWriteTrunc: + case ReadWriteTruncBinary: + delegate = new PipeReadWriteConnection(this, command); + break; + default: + throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + getOpenMode()); + } + setDelegate(delegate); + } + + @Override + public String getSummaryDescription() { + return command; + } + } + + static class PipeReadRConnection extends DelegateReadRConnection { + private final ByteChannel channel; + + protected PipeReadRConnection(BaseRConnection base, String command) throws IOException { + super(base); + Process p = PipeConnections.executeAndJoin(command); + channel = ConnectionSupport.newChannel(p.getInputStream()); + } + + @Override + public ByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + } + + private static class PipeWriteConnection extends DelegateWriteRConnection { + private final ByteChannel channel; + + PipeWriteConnection(BaseRConnection base, String command) throws IOException { + super(base); + Process p = PipeConnections.executeAndJoin(command); + channel = ConnectionSupport.newChannel(p.getOutputStream()); + } + + @Override + public ByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + } + + private static class PipeReadWriteConnection extends DelegateReadWriteRConnection { + + private final RWChannel channel; + + protected PipeReadWriteConnection(BaseRConnection base, String command) throws IOException { + super(base); + Process p = PipeConnections.executeAndJoin(command); + channel = new RWChannel(p.getInputStream(), p.getOutputStream()); + } + + @Override + public ByteChannel getChannel() { + return channel; + } + + @Override + public boolean isSeekable() { + return false; + } + + private static final class RWChannel implements ByteChannel { + private final ReadableByteChannel rchannel; + private final WritableByteChannel wchannel; + + RWChannel(InputStream in, OutputStream out) { + rchannel = Channels.newChannel(in); + wchannel = Channels.newChannel(out); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return rchannel.read(dst); + } + + @Override + public boolean isOpen() { + return rchannel.isOpen() && wchannel.isOpen(); + } + + @Override + public void close() throws IOException { + rchannel.close(); + wchannel.close(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + return wchannel.write(src); + } + + } + + } + +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java index b699372ccaae032d19a0777b141c5d47ba662ce7..341d12e3ba8fbdd49f07a197ab28d790f74acbb1 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,156 +26,64 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.LinkedList; +import java.nio.channels.ByteChannel; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; import com.oracle.truffle.r.runtime.context.RContext; -import com.oracle.truffle.r.runtime.data.RAttributesLayout; -import com.oracle.truffle.r.runtime.data.RDataFactory; -import com.oracle.truffle.r.runtime.data.RExternalPtr; -import com.oracle.truffle.r.runtime.data.RNull; -import com.oracle.truffle.r.runtime.data.RStringVector; -import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; /** * Denotes an R {@code connection} instance used in the {@code base} I/O library. - * - * TODO Refactor the pushBack code into ConnectionsSupport */ -public abstract class RConnection implements AutoCloseable { - - public final RAbstractIntVector asVector() { - String[] classes = new String[]{ConnectionSupport.getBaseConnection(this).getConnectionClass().getPrintName(), "connection"}; - - RAbstractIntVector result = RDataFactory.createIntVector(new int[]{getDescriptor()}, true); - - RStringVector classVector = RDataFactory.createStringVector(classes, RDataFactory.COMPLETE_VECTOR); - // it's important to put "this" into the externalptr, so that it doesn't get collected - RExternalPtr connectionId = RDataFactory.createExternalPtr(null, this, RDataFactory.createSymbol("connection"), RNull.instance); - DynamicObject attrs = RAttributesLayout.createClassWithConnId(classVector, connectionId); - result.initAttributes(attrs); - return result; - } +public interface RConnection extends AutoCloseable { - public static BaseRConnection fromIndex(int con) { + static BaseRConnection fromIndex(int con) { return RContext.getInstance().stateRConnection.getConnection(con, true); } - private LinkedList<String> pushBack; - - public abstract String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException; - - private String readOneLineWithPushBack(String[] res, int ind, @SuppressWarnings("unused") boolean warn, @SuppressWarnings("unused") boolean skipNul) { - String s = pushBack.pollLast(); - if (s == null) { - return null; - } else { - String[] lines = s.split("\n", 2); - if (lines.length == 2) { - // we hit end of the line - if (lines[1].length() != 0) { - // suffix is not empty and needs to be processed later - pushBack.push(lines[1]); - } - res[ind] = lines[0]; - return null; - } else { - // no end of the line found yet - StringBuilder sb = new StringBuilder(); - do { - assert lines.length == 1; - sb.append(lines[0]); - s = pushBack.pollLast(); - if (s == null) { - break; - } - - lines = s.split("\n", 2); - if (lines.length == 2) { - // we hit end of the line - if (lines[1].length() != 0) { - // suffix is not empty and needs to be processed later - pushBack.push(lines[1]); - } - res[ind] = sb.append(lines[0]).toString(); - return null; - } // else continue - } while (true); - return sb.toString(); - } - } - } - - @TruffleBoundary - private String[] readLinesWithPushBack(int n, boolean warn, boolean skipNul) throws IOException { - String[] res = new String[n]; - for (int i = 0; i < n; i++) { - String s = readOneLineWithPushBack(res, i, warn, skipNul); - if (s == null) { - if (res[i] == null) { - // no more push back value - System.arraycopy(readLinesInternal(n - i, warn, skipNul), 0, res, i, n - i); - pushBack = null; - break; - } - // else res[i] has been filled - move to trying to fill the next one - } else { - // reached the last push back value without reaching and of line - assert pushBack.size() == 0; - System.arraycopy(readLinesInternal(n - i, warn, skipNul), 0, res, i, n - i); - res[i] = s + res[i]; - pushBack = null; - break; - } - } - return res; - } - /** - * Read (n > 0 up to n else unlimited) lines on the connection. + * Return the underlying input stream (for internal use).<br> + * <p> + * <b>NOTE:</b> The connection may do some caching internally! Therefore, the behavior is + * undefined if you mix using the input stream directly and using the read methods of the + * connection. + * </p> * */ - @TruffleBoundary - public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { - if (pushBack == null) { - return readLinesInternal(n, warn, skipNul); - } else if (pushBack.size() == 0) { - pushBack = null; - return readLinesInternal(n, warn, skipNul); - } else { - return readLinesWithPushBack(n, warn, skipNul); - } - } + InputStream getInputStream() throws IOException; /** - * Return the underlying input stream (for internal use). TODO Replace with a more principled - * solution. + * Return the underlying output stream (for internal use).<br> + * <p> + * <b>NOTE:</b> The connection may do some caching internally! Therefore, the behavior is + * undefined if you mix using the output stream directly and using the write methods of the + * connection. + * </p> */ - public abstract InputStream getInputStream() throws IOException; + OutputStream getOutputStream() throws IOException; /** - * Return the underlying output stream (for internal use). TODO Replace with a more principled - * solution. + * Return the underlying byte channel (for internal use). + * + * @throws IOException */ - public abstract OutputStream getOutputStream() throws IOException; + ByteChannel getChannel() throws IOException; /** * Close the connection. The corresponds to the {@code R close} function. */ - public abstract void closeAndDestroy() throws IOException; + void closeAndDestroy() throws IOException; /** * Returns {@ode true} iff we can read on this connection. */ - public abstract boolean canRead(); + boolean canRead(); /** * Returns {@ode true} iff we can write on this connection. */ - public abstract boolean canWrite(); + boolean canWrite(); /** * Forces the connection open. If the connection was already open does nothing. Otherwise, tries @@ -198,56 +106,23 @@ public abstract class RConnection implements AutoCloseable { * rely on it but should use the result in the body of the {@code try} block. If the connection * cannot be opened {@link IOException} is thrown. */ - public abstract RConnection forceOpen(String modeString) throws IOException; + RConnection forceOpen(String modeString) throws IOException; /** * Closes the internal state of the stream, but does not set the connection state to "closed", * i.e., allowing it to be re-opened. */ @Override - public abstract void close() throws IOException; + void close() throws IOException; - /** - * Pushes lines back to the connection. - */ - @TruffleBoundary - public final void pushBack(RAbstractStringVector lines, boolean addNewLine) { - if (pushBack == null) { - pushBack = new LinkedList<>(); - } - for (int i = 0; i < lines.getLength(); i++) { - String newLine = lines.getDataAt(i); - if (addNewLine) { - newLine = newLine + '\n'; - } - pushBack.addFirst(newLine); - } - } - - /** - * Return the length of the push back. - */ - @TruffleBoundary - public final int pushBackLength() { - return pushBack == null ? 0 : pushBack.size(); - } - - /** - * Clears the pushback. - */ - @TruffleBoundary - public final void pushBackClear() { - pushBack = null; - } - - public enum SeekMode { + enum SeekMode { ENQUIRE, START, CURRENT, END } - public enum SeekRWMode { + enum SeekRWMode { LAST, READ, WRITE @@ -256,27 +131,27 @@ public abstract class RConnection implements AutoCloseable { /** * Support for {@code isSeekable} Internal. */ - public abstract boolean isSeekable(); + boolean isSeekable(); /** - * Support for {@code seek} Internal. + * Allows to seek in the connection. */ - public abstract long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException; + long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException; /** * Internal support for reading one character at a time. */ - public abstract int getc() throws IOException; + int getc() throws IOException; /** * Write the {@code lines} to the connection, with {@code sep} appended after each "line". N.B. * The output will only appear as a sequence of lines if {@code sep == "\n"}. */ - public abstract void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException; + void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException; - public abstract void flush() throws IOException; + void flush() throws IOException; - public abstract int getDescriptor(); + int getDescriptor(); /** * Writes {@code s} optionally followed by a newline to the connection. This does not correspond @@ -284,7 +159,7 @@ public abstract class RConnection implements AutoCloseable { * Since these can be diverted by the {@code sink} builtin, every output connection class must * support this. */ - public abstract void writeString(String s, boolean nl) throws IOException; + void writeString(String s, boolean nl) throws IOException; /** * Internal connection-specific support for the {@code writeChar} builtin. @@ -294,20 +169,20 @@ public abstract class RConnection implements AutoCloseable { * @param eos string to append to s * @param useBytes TODO */ - public abstract void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException; + void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException; /** * Internal connection-specific support for the {@code readChar} builtin. * * @param nchars number of characters to read */ - public abstract String readChar(int nchars, boolean useBytes) throws IOException; + String readChar(int nchars, boolean useBytes) throws IOException; /** * Internal connection-specific support for the {@code writeBin} builtin. The implementation * should attempt to write all the data, denoted by {@code buffer.remaining()}. */ - public abstract void writeBin(ByteBuffer buffer) throws IOException; + void writeBin(ByteBuffer buffer) throws IOException; /** * Internal connection-specific support for the {@code readBin} builtin. The buffer is allocated @@ -315,7 +190,7 @@ public abstract class RConnection implements AutoCloseable { * should attempt to read that much data, returning the actual number read as the result. EOS is * denoted by a return value of zero. */ - public abstract int readBin(ByteBuffer buffer) throws IOException; + int readBin(ByteBuffer buffer) throws IOException; /** * Internal connection-specific support for the {@code readBin} builtin on character data. @@ -324,16 +199,23 @@ public abstract class RConnection implements AutoCloseable { * implies that no data was read. The caller must locate the null terminator to determine the * length of the string. */ - public abstract byte[] readBinChars() throws IOException; + byte[] readBinChars() throws IOException; + + /** + * Read (n > 0 up to n else unlimited) lines on the connection. + */ + @TruffleBoundary + String[] readLines(int n, boolean warn, boolean skipNul) throws IOException; /** * Returns {@code true} iff this is a text mode connection. */ - public abstract boolean isTextMode(); + boolean isTextMode(); /** * Returns {@code true} iff this connection is open. */ - public abstract boolean isOpen(); + boolean isOpen(); + void pushBack(RAbstractStringVector lines, boolean addNewLine); } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RawConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RawConnections.java index 02d6e0dd6c3809506191ce9fb7076ad85ab4a366..3213cfc97fe70ac6131796149f234e9f0b46c4ad 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RawConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RawConnections.java @@ -23,23 +23,15 @@ package com.oracle.truffle.r.runtime.conn; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; import java.util.Objects; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadWriteRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateWriteRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ReadWriteHelper; -import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; public class RawConnections { @@ -99,7 +91,7 @@ public class RawConnections { return channel.getBuffer(); } - public static long seek(SeekableMemoryByteChannel channel, long offset, SeekMode seekMode, @SuppressWarnings("unused") SeekRWMode seekRWMode) throws IOException { + private static long seek(SeekableByteChannel channel, long offset, SeekMode seekMode, @SuppressWarnings("unused") SeekRWMode seekRWMode) throws IOException { final long origPos = channel.position(); final long newPos; switch (seekMode) { @@ -122,7 +114,7 @@ public class RawConnections { } - static class RawReadRConnection extends DelegateReadRConnection implements ReadWriteHelper { + static class RawReadRConnection extends DelegateReadRConnection { private SeekableMemoryByteChannel channel; RawReadRConnection(BaseRConnection base, SeekableMemoryByteChannel channel) { @@ -139,14 +131,9 @@ public class RawConnections { super(base); } - @Override - public int readBin(ByteBuffer buffer) throws IOException { - return channel.read(buffer); - } - @Override public byte[] readBinChars() throws IOException { - // acquire copy from buffer without advancing the cursor + // acquire copy from buffer without advancing the cursorskipNul final byte[] buffer = channel.getBufferFromCursor(); int i = 0; while (i < buffer.length && buffer[i] != (byte) 0) { @@ -160,45 +147,23 @@ public class RawConnections { return new byte[0]; } - @TruffleBoundary - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - return readLinesHelper(channel.getInputStream(), n, warn, skipNul); - } - - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, channel.getInputStream(), useBytes); - } - - @Override - public InputStream getInputStream() throws IOException { - return channel.getInputStream(); - } - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + return RawRConnection.seek(channel, offset, seekMode, seekRWMode); } @Override - public void close() throws IOException { - channel.close(); + public SeekableByteChannel getChannel() { + return channel; } @Override - public OutputStream getOutputStream() { - throw RError.error(RError.SHOW_CALLER2, RError.Message.NOT_AN_OUTPUT_RAW_CONNECTION); - } - - @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { - return RawRConnection.seek(channel, offset, seekMode, seekRWMode); + public boolean isSeekable() { + return true; } } - private static class RawWriteBinaryConnection extends DelegateWriteRConnection implements ReadWriteHelper { + private static class RawWriteBinaryConnection extends DelegateWriteRConnection { private final SeekableMemoryByteChannel channel; RawWriteBinaryConnection(BaseRConnection base, SeekableMemoryByteChannel channel, boolean append) { @@ -214,67 +179,31 @@ public class RawConnections { } @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - writeCharHelper(channel.getOutputStream(), s, pad, eos); - } - - @Override - public void writeBin(ByteBuffer buffer) throws IOException { - channel.write(buffer); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return channel.getOutputStream(); - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); - } - - @Override - public void close() throws IOException { - flush(); - channel.close(); - } - - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - for (int i = 0; i < lines.getLength(); i++) { - String line = lines.getDataAt(i); - channel.write(line.getBytes()); - channel.write(sep.getBytes()); - } - } - - @Override - public void writeString(String s, boolean nl) throws IOException { - writeStringHelper(channel.getOutputStream(), s, nl); + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + return RawRConnection.seek(channel, offset, seekMode, seekRWMode); } @Override - public void flush() throws IOException { - // nothing to do + public SeekableByteChannel getChannel() { + return channel; } @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { - return RawRConnection.seek(channel, offset, seekMode, seekRWMode); + public boolean isSeekable() { + return true; } } - private static class RawReadWriteConnection extends DelegateReadWriteRConnection implements ReadWriteHelper { + private static class RawReadWriteConnection extends DelegateReadWriteRConnection { private final SeekableMemoryByteChannel channel; protected RawReadWriteConnection(BaseRConnection base, SeekableMemoryByteChannel channel, boolean append) { super(base); - this.channel = Objects.requireNonNull(channel); - if (!append) { + this.channel = channel; + if (append) { try { - channel.position(0); + channel.position(channel.size()); } catch (IOException e) { RInternalError.shouldNotReachHere(); } @@ -282,79 +211,18 @@ public class RawConnections { } @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - throw RInternalError.unimplemented(); - } - - @Override - public InputStream getInputStream() throws IOException { - return channel.getInputStream(); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return channel.getOutputStream(); - } - - @Override - public void closeAndDestroy() throws IOException { - close(); - } - - @Override - public void close() throws IOException { - channel.close(); - } - - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - for (int i = 0; i < lines.getLength(); i++) { - channel.write(lines.getDataAt(i).getBytes()); - channel.write(sep.getBytes()); - } - } - - @Override - public void flush() throws IOException { - // nothing to do - } - - @Override - public void writeString(String s, boolean nl) throws IOException { - channel.write(s.getBytes()); - if (nl) { - channel.write(System.lineSeparator().getBytes()); - } - } - - @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - writeCharHelper(channel.getOutputStream(), s, pad, eos); - } - - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, channel.getInputStream(), useBytes); - } - - @Override - public void writeBin(ByteBuffer buffer) throws IOException { - channel.write(buffer); - } - - @Override - public int readBin(ByteBuffer buffer) throws IOException { - return channel.read(buffer); + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + return RawRConnection.seek(channel, offset, seekMode, seekRWMode); } @Override - public byte[] readBinChars() throws IOException { - throw RInternalError.unimplemented(); + public SeekableByteChannel getChannel() { + return channel; } @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { - return RawRConnection.seek(channel, offset, seekMode, seekRWMode); + public boolean isSeekable() { + return true; } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SeekableMemoryByteChannel.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SeekableMemoryByteChannel.java index 79dd7dfe9ef7fec4d1905242344c39aaa01cb6c7..c625c855a30a84a941f19ceed6f05d377e14fe6f 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SeekableMemoryByteChannel.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SeekableMemoryByteChannel.java @@ -89,7 +89,7 @@ public class SeekableMemoryByteChannel implements SeekableByteChannel { this(clp2(Objects.requireNonNull(initialBuffer).length)); assert buf.length >= initialBuffer.length; endPos = initialBuffer.length; - position = initialBuffer.length; + position = 0L; System.arraycopy(initialBuffer, 0, buf, 0, initialBuffer.length); } @@ -105,9 +105,10 @@ public class SeekableMemoryByteChannel implements SeekableByteChannel { } Objects.requireNonNull(dst); RInternalError.guarantee(dst.remaining() <= Integer.MAX_VALUE); - final int nCanRead = (int) Math.min(dst.remaining(), size()); + final int nCanRead = (int) Math.min(dst.remaining(), remaining()); dst.put(buf, (int) (position - offset), nCanRead); position += nCanRead; + assert position <= endPos; return nCanRead; } @@ -181,11 +182,18 @@ public class SeekableMemoryByteChannel implements SeekableByteChannel { return endPos - offset; } + private long remaining() { + return endPos - position; + } + @Override public SeekableByteChannel position(long newPosition) throws IOException { if (newPosition < 0) { throw new IllegalArgumentException("newPosition must not be negative"); } + if (newPosition + offset > endPos) { + throw new IllegalArgumentException("setting position outside data range is currently not supported"); + } position = newPosition + offset; return this; } @@ -224,10 +232,13 @@ public class SeekableMemoryByteChannel implements SeekableByteChannel { } public int read() throws IOException { - final ByteBuffer bf = ByteBuffer.allocate(1); - read(bf); - bf.rewind(); - return bf.get(); + if (position < endPos) { + final ByteBuffer bf = ByteBuffer.allocate(1); + read(bf); + bf.rewind(); + return bf.get(); + } + return -1; } private class SyncOS extends OutputStream { diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java index e7da9ceca6c5ac57c0273d57c0c631a8dc9898e9..2f978e6fa943aabad0c82ecf873bfd52a925a407 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java @@ -23,21 +23,15 @@ package com.oracle.truffle.r.runtime.conn; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.InetSocketAddress; -import java.net.ServerSocket; import java.net.Socket; -import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadWriteRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ReadWriteHelper; -import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; public class SocketConnections { /** @@ -50,22 +44,29 @@ public class SocketConnections { protected final boolean server; protected final String host; protected final int port; - protected final boolean blocking; protected final int timeout; - public RSocketConnection(String modeString, boolean server, String host, int port, boolean blocking, int timeout) throws IOException { - super(ConnectionClass.Socket, modeString, AbstractOpenMode.Read); + public RSocketConnection(String modeString, boolean server, String host, int port, boolean blocking, int timeout, String encoding) throws IOException { + super(ConnectionClass.Socket, modeString, AbstractOpenMode.Read, blocking, encoding); this.server = server; this.host = host; this.port = port; - this.blocking = blocking; this.timeout = timeout; openNonLazyConnection(); } @Override protected void createDelegateConnection() throws IOException { - DelegateRConnection delegate = server ? new RServerSocketConnection(this) : new RClientSocketConnection(this); + DelegateRConnection delegate; + if (server) { + delegate = new RServerSocketConnection(this); + } else { + if (isBlocking()) { + delegate = new RClientSocketConnection(this); + } else { + delegate = new RClientSocketNonBlockConnection(this); + } + } setDelegate(delegate); } @@ -75,11 +76,9 @@ public class SocketConnections { } } - private abstract static class RSocketReadWriteConnection extends DelegateReadWriteRConnection implements ReadWriteHelper { + private abstract static class RSocketReadWriteConnection extends DelegateReadWriteRConnection { private Socket socket; - private SocketChannel socketChannel; - protected InputStream inputStream; - protected OutputStream outputStream; + private SocketChannel channel; protected final RSocketConnection thisBase; protected RSocketReadWriteConnection(RSocketConnection base) { @@ -87,10 +86,11 @@ public class SocketConnections { this.thisBase = base; } - protected void openStreams(Socket socketArg) throws IOException { - this.socket = socketArg; - this.socketChannel = socket.getChannel(); - if (thisBase.blocking) { + protected void openStreams(SocketChannel socketArg) throws IOException { + channel = socketArg; + socket = socketArg.socket(); + if (thisBase.isBlocking()) { + channel.configureBlocking(true); // Java (int) timeouts do not meet the POSIX standard of 31 days long millisTimeout = ((long) thisBase.timeout) * 1000; if (millisTimeout > Integer.MAX_VALUE) { @@ -98,95 +98,69 @@ public class SocketConnections { } socket.setSoTimeout((int) millisTimeout); } else { - socketChannel.configureBlocking(false); + channel.configureBlocking(false); } - inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); - } - - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - return readLinesHelper(inputStream, n, warn, skipNul); - } - - @Override - public InputStream getInputStream() throws IOException { - return inputStream; - } - - @Override - public OutputStream getOutputStream() throws IOException { - return outputStream; - } - - @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - writeLinesHelper(outputStream, lines, sep); - } - - @Override - public void writeBin(ByteBuffer buffer) throws IOException { - writeBinHelper(buffer, outputStream); } @Override - public int readBin(ByteBuffer buffer) throws IOException { - return readBinHelper(buffer, inputStream); + public ByteChannel getChannel() { + return channel; } @Override - public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - writeCharHelper(outputStream, s, pad, eos); + public boolean isSeekable() { + return false; } + } - @Override - public void writeString(String s, boolean nl) throws IOException { - writeStringHelper(outputStream, s, nl); - } + private abstract static class RSocketReadWriteNonBlockConnection extends DelegateReadWriteRConnection { + private Socket socket; + private SocketChannel socketChannel; - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, inputStream, useBytes); + protected RSocketReadWriteNonBlockConnection(RSocketConnection base) { + super(base); } - @Override - public byte[] readBinChars() throws IOException { - return readBinCharsHelper(inputStream); + protected void openStreams(Socket socketArg) throws IOException { + this.socket = socketArg; + this.socketChannel = socket.getChannel(); + socketChannel.configureBlocking(false); } @Override - public void flush() throws IOException { - outputStream.flush(); + public void close() throws IOException { + socketChannel.close(); + socket.close(); } @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); + public ByteChannel getChannel() { + return socketChannel; } @Override - public void close() throws IOException { - socket.close(); + public boolean isSeekable() { + return false; } } private static class RServerSocketConnection extends RSocketReadWriteConnection { - private final Socket connectionSocket; + private final SocketChannel connectionSocket; RServerSocketConnection(RSocketConnection base) throws IOException { super(base); InetSocketAddress addr = new InetSocketAddress(base.port); - ServerSocket serverSocket = new ServerSocket(); + ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); + // we expect only one connection per-server socket; furthermore, we need to accommodate // for multiple connections being established locally on the same server port; // consequently, we close the server socket at the end of the constructor and allow // address reuse to be able to open the next connection after the current one closes - serverSocket.setReuseAddress(true); - serverSocket.bind(addr); - connectionSocket = serverSocket.accept(); + serverSocketChannel.socket().setReuseAddress(true); + serverSocketChannel.socket().bind(addr); + connectionSocket = serverSocketChannel.accept(); openStreams(connectionSocket); - serverSocket.close(); + serverSocketChannel.close(); } @Override @@ -199,6 +173,15 @@ public class SocketConnections { private static class RClientSocketConnection extends RSocketReadWriteConnection { RClientSocketConnection(RSocketConnection base) throws IOException { + super(base); + SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(base.host, base.port)); + openStreams(socketChannel); + } + } + + private static class RClientSocketNonBlockConnection extends RSocketReadWriteNonBlockConnection { + + RClientSocketNonBlockConnection(RSocketConnection base) throws IOException { super(base); SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(base.host, base.port)); openStreams(socketChannel.socket()); diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java index 22f8ca5d238f1834edc99797045c60ffdbac47c8..6150dc902b2cf11456b38457c64e489a62bf8f12 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -79,15 +79,15 @@ public class StdConnections { return RContext.getInstance().stateStdConnections; } - public static RConnection getStdin() { + public static BaseRConnection getStdin() { return getContextState().stdin; } - public static RConnection getStdout() { + public static BaseRConnection getStdout() { return getContextState().stdout; } - public static RConnection getStderr() { + public static BaseRConnection getStderr() { return getContextState().stderr; } @@ -161,7 +161,7 @@ public class StdConnections { } @Override - public RConnection forceOpen(String modeString) { + public BaseRConnection forceOpen(String modeString) { return this; } @@ -171,7 +171,7 @@ public class StdConnections { } @Override - public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + public long seekInternal(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { throw RError.error(RError.SHOW_CALLER2, RError.Message.UNSEEKABLE_CONNECTION); } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java index 9434f758c41dffdab375149fbc3da7d08555a24d..e789830946419241102ac19c33223ddb19c7cb50 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java @@ -23,9 +23,9 @@ package com.oracle.truffle.r.runtime.conn; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; import java.util.ArrayList; import com.oracle.truffle.r.runtime.RError; @@ -33,9 +33,6 @@ import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateWriteRConnection; import com.oracle.truffle.r.runtime.data.RDataFactory; import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; @@ -45,7 +42,7 @@ import com.oracle.truffle.r.runtime.env.REnvironment.PutException; public class TextConnections { public static class TextRConnection extends BaseRConnection { protected String description; - protected RAbstractStringVector object; + private final RAbstractStringVector object; protected REnvironment env; public TextRConnection(String description, RAbstractStringVector object, REnvironment env, String modeString) throws IOException { @@ -66,10 +63,14 @@ public class TextConnections { DelegateRConnection delegate = null; switch (getOpenMode().abstractOpenMode) { case Read: - delegate = new TextReadRConnection(this); + if (object != null) { + delegate = new TextReadRConnection(this, object); + } else { + throw RError.error(RError.SHOW_CALLER2, RError.Message.INVALID_ARGUMENT, "text"); + } break; case Write: - delegate = new TextWriteRConnection(this); + delegate = new TextWriteRConnection(this, object); break; default: throw RError.nyi(RError.SHOW_CALLER2, "open mode: " + getOpenMode().modeString); @@ -77,24 +78,25 @@ public class TextConnections { setDelegate(delegate); } - public String[] getValue() { + public RAbstractStringVector getValue() { return ((GetConnectionValue) theConnection).getValue(); } } private interface GetConnectionValue { - String[] getValue(); + RAbstractStringVector getValue(); } private static class TextReadRConnection extends DelegateReadRConnection implements GetConnectionValue { private final String[] lines; private int index; - TextReadRConnection(TextRConnection base) { + TextReadRConnection(TextRConnection base, RAbstractStringVector object) { super(base); + assert object != null; StringBuffer sb = new StringBuffer(); - for (int i = 0; i < base.object.getLength(); i++) { - sb.append(base.object.getDataAt(i)); + for (int i = 0; i < object.getLength(); i++) { + sb.append(object.getDataAt(i)); // vector elements are implicitly terminated with a newline sb.append('\n'); } @@ -102,7 +104,7 @@ public class TextConnections { } @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { + public String[] readLines(int n, boolean warn, boolean skipNul) throws IOException { int nleft = lines.length - index; int nlines = nleft; if (n > 0) { @@ -115,44 +117,24 @@ public class TextConnections { return result; } - @SuppressWarnings("hiding") @Override - public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { - throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); - } - - @Override - public InputStream getInputStream() throws IOException { - throw RInternalError.shouldNotReachHere(); - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - } - - @Override - public void close() { - } - - @Override - public int readBin(ByteBuffer buffer) throws IOException { - throw RError.nyi(null, "readBin on text connection"); + public void close() throws IOException { + // nothing to do } @Override - public byte[] readBinChars() throws IOException { - throw RError.nyi(null, "readBinChars on text connection"); + public boolean isSeekable() { + return false; } @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - throw RError.nyi(null, "readChar on text connection"); + public ByteChannel getChannel() { + throw RInternalError.shouldNotReachHere(); } @Override - public String[] getValue() { - throw RError.nyi(null, "textConnectionValue"); + public RAbstractStringVector getValue() { + throw RError.error(RError.SHOW_CALLER2, RError.Message.NOT_AN_OUTPUT_TEXT_CONNECTION); } } @@ -160,31 +142,39 @@ public class TextConnections { private String incompleteLine; private RStringVector textVec; private String idName; + private RAbstractStringVector object; + + /** Indicates if the connection is anonymous, i.e., not input object has been provided. */ + private final boolean anonymous; private void initTextVec(RStringVector v, TextRConnection textBase) { - if (textBase.description.equals("NULL")) { - throw RError.nyi(null, "anonymous text output connection"); - } - idName = textBase.object.getDataAt(0); - try { - textVec = v; - textBase.env.put(idName, textVec); - } catch (PutException ex) { - throw RError.error(RError.SHOW_CALLER2, ex); + + if (anonymous) { + object = v; + } else { + idName = object.getDataAt(0); + try { + textVec = v; + textBase.env.put(idName, textVec); + } catch (PutException ex) { + throw RError.error(RError.SHOW_CALLER2, ex); + } + // lock the binding + textBase.env.lockBinding(idName); } - // lock the binding - textBase.env.lockBinding(idName); } - protected TextWriteRConnection(BaseRConnection base) { + protected TextWriteRConnection(BaseRConnection base, RAbstractStringVector object) { super(base); + this.object = object; + this.anonymous = object == null; TextRConnection textBase = (TextRConnection) base; initTextVec(RDataFactory.createStringVector(0), textBase); } @Override - public OutputStream getOutputStream() throws IOException { - return new ConnectionOutputStream(); + public ByteChannel getChannel() { + return ConnectionSupport.newChannel(new ConnectionOutputStream()); } @Override @@ -193,10 +183,17 @@ public class TextConnections { if (incompleteLine != null) { appendData(new String[]{incompleteLine}); incompleteLine = null; + base.setIncomplete(false); } base.closed = true; TextRConnection textBase = (TextRConnection) base; - textBase.env.unlockBinding(idName); + unlockBinding(textBase); + } + + private void unlockBinding(TextRConnection textBase) { + if (idName != null) { + textBase.env.unlockBinding(idName); + } } @Override @@ -210,8 +207,9 @@ public class TextConnections { ArrayList<String> appendedLines = new ArrayList<>(); while ((nlIndex = result.indexOf('\n', px)) >= 0) { if (incompleteLine != null) { - appendedLines.add(new StringBuffer(incompleteLine).append(result.substring(px, nlIndex)).toString()); + appendedLines.add(new StringBuilder(incompleteLine).append(result.substring(px, nlIndex)).toString()); incompleteLine = null; + base.setIncomplete(false); } else { appendedLines.add(result.substring(px, nlIndex)); } @@ -222,9 +220,11 @@ public class TextConnections { if (incompleteLine != null && !endOfLine) { // end of line not found - accumulate incomplete line incompleteLine = new StringBuffer(incompleteLine).append(result).toString(); + base.setIncomplete(true); } else if (px < result.length()) { // only reset incompleteLine if incompleteLine = result.substring(px); + base.setIncomplete(true); } if (appendedLines.size() > 0) { // update the vector data @@ -235,7 +235,7 @@ public class TextConnections { } void appendData(String[] appendedData) { - String[] existingData = textVec.getDataWithoutCopying(); + String[] existingData = textVec != null ? textVec.getDataWithoutCopying() : new String[0]; String[] updateData = appendedData; if (existingData.length > 0) { updateData = new String[existingData.length + appendedData.length]; @@ -243,11 +243,7 @@ public class TextConnections { System.arraycopy(appendedData, 0, updateData, existingData.length, appendedData.length); } TextRConnection textBase = (TextRConnection) base; - /* - * N.B. This assumes one thread per RContext else another thread could be calling - * lockBinding - */ - textBase.env.unlockBinding(idName); + unlockBinding(textBase); // TODO: is vector really complete? initTextVec(RDataFactory.createStringVector(updateData, RDataFactory.COMPLETE_VECTOR), textBase); } @@ -258,6 +254,7 @@ public class TextConnections { if (incompleteLine != null) { sb.append(incompleteLine); incompleteLine = null; + base.setIncomplete(false); } for (int i = 0; i < lines.getLength(); i++) { sb.append(lines.getDataAt(i)); @@ -277,17 +274,17 @@ public class TextConnections { @Override public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException { - throw RError.nyi(null, "writeChar on text connection"); + throw RError.error(RError.SHOW_CALLER2, RError.Message.NOT_ENABLED_FOR_THIS_CONN, "write"); } @Override public void writeBin(ByteBuffer buffer) throws IOException { - throw RError.nyi(null, "writeBin on text connection"); + throw RError.error(RError.SHOW_CALLER2, RError.Message.ONLY_WRITE_BINARY_CONNECTION); } @Override - public String[] getValue() { - throw RError.nyi(null, "textConnectionValue"); + public RAbstractStringVector getValue() { + return object; } private class ConnectionOutputStream extends OutputStream { @@ -315,6 +312,16 @@ public class TextConnections { throw RInternalError.unimplemented(); } } + + @Override + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + throw RError.error(RError.SHOW_CALLER, RError.Message.SEEK_NOT_RELEVANT_FOR_TEXT_CON); + } + + @Override + public boolean isSeekable() { + return false; + } } /** diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/URLConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/URLConnections.java index 7aad2be8e1da673e7e06da3e01258bc900eccacf..9bd5460030ab1a4fe14e3008922472f7c7ff978d 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/URLConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/URLConnections.java @@ -24,25 +24,21 @@ package com.oracle.truffle.r.runtime.conn; import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.AbstractOpenMode; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.BaseRConnection; import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ConnectionClass; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.DelegateReadRConnection; -import com.oracle.truffle.r.runtime.conn.ConnectionSupport.ReadWriteHelper; public class URLConnections { public static class URLRConnection extends BaseRConnection { protected final String urlString; - public URLRConnection(String url, String modeString) throws IOException { - super(ConnectionClass.URL, modeString, AbstractOpenMode.Read); + public URLRConnection(String url, String modeString, String encoding) throws IOException { + super(ConnectionClass.URL, modeString, AbstractOpenMode.Read, encoding); this.urlString = url; } @@ -56,6 +52,7 @@ public class URLConnections { DelegateRConnection delegate = null; switch (getOpenMode().abstractOpenMode) { case Read: + case ReadBinary: delegate = new URLReadRConnection(this); break; default: @@ -65,50 +62,24 @@ public class URLConnections { } } - private static class URLReadRConnection extends DelegateReadRConnection implements ReadWriteHelper { + private static class URLReadRConnection extends DelegateReadRConnection { - private final BufferedInputStream inputStream; + private final ByteChannel rchannel; protected URLReadRConnection(URLRConnection base) throws MalformedURLException, IOException { super(base); URL url = new URL(base.urlString); - inputStream = new BufferedInputStream(url.openStream()); + rchannel = ConnectionSupport.newChannel(new BufferedInputStream(url.openStream())); } @Override - public int readBin(ByteBuffer buffer) throws IOException { - throw RError.nyi(null, "readBin on URL"); + public ByteChannel getChannel() { + return rchannel; } @Override - public byte[] readBinChars() throws IOException { - throw RError.nyi(null, "readBinChars on URL"); - } - - @Override - public String[] readLinesInternal(int n, boolean warn, boolean skipNul) throws IOException { - return readLinesHelper(inputStream, n, warn, skipNul); - } - - @Override - public InputStream getInputStream() throws IOException { - return inputStream; - } - - @Override - public void closeAndDestroy() throws IOException { - base.closed = true; - close(); - } - - @Override - public void close() throws IOException { - inputStream.close(); - } - - @Override - public String readChar(int nchars, boolean useBytes) throws IOException { - return readCharHelper(nchars, inputStream, useBytes); + public boolean isSeekable() { + return false; } } } diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test index 4e3506d9a43fb5af9a7bd5618430986cf82c8824..88bd852e6cc7cb1b6056daaa27433829351ce472 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test @@ -21071,6 +21071,19 @@ Levels: c f h k m n p x [1000] 1 Levels: 0 1 +##com.oracle.truffle.r.test.builtins.TestBuiltin_fifoConnection.testFifoOpenInexisting# +#capabilities("fifo") +fifo +TRUE + +##com.oracle.truffle.r.test.builtins.TestBuiltin_fifoConnection.testFifoOpenInexisting#Output.IgnoreErrorContext#Output.IgnoreWarningContext# +#{ zz <- fifo("/tmp/pipe3408688236", "r", blocking = TRUE); close(zz); } +Error in fifo("/tmp/pipe3408688236", "r", blocking = TRUE) : + cannot open the connection +In addition: Warning message: +In fifo("/tmp/pipe3408688236", "r", blocking = TRUE) : + cannot open fifo '/tmp/pipe3408688236' + ##com.oracle.truffle.r.test.builtins.TestBuiltin_fileaccess.testfileaccess1# #argv <- list(character(0), 0); .Internal(file.access(argv[[1]], argv[[2]])) integer(0) @@ -27878,7 +27891,7 @@ Error in lengths(quote(a)) : 'x' must be a list or atomic vector #{ x <- 1 ; levels(x)<-4.5; levels(x);} [1] 4.5 -##com.oracle.truffle.r.test.builtins.TestBuiltin_levels.testLevels# +##com.oracle.truffle.r.test.builtins.TestBuiltin_levels.testLevels#Output.MayIgnoreErrorContext# #{ x <- 1 ; levels(x)<-NULL; levels(notx)} Error in levels(notx) : object 'notx' not found @@ -42177,82 +42190,6 @@ numeric(0) #argv <- structure(list(length = 0), .Names = 'length');do.call('raw', argv) raw(0) -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.runRSourceTests# -#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_readBin.R") } - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.runRSourceTests# -#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_readWriteBin.R") } - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.runRSourceTests# -#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_seek.R") } - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.runRSourceTests# -#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_writeBin.R") } - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadAppendText# -#{ rc <- rawConnection(raw(0), "a+"); close(rc); write(charToRaw("A"), rc) } -Error in cat(x, file = file, sep = c(rep.int(sep, ncolumns - 1), "\n"), : - invalid connection - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadAppendText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); write(charToRaw(", World"), rc); res <- rawConnectionValue(rc); close(rc); res } - [1] 48 65 6c 6c 6f 32 63 20 32 30 20 35 37 20 36 66 20 37 32 0a 36 63 20 36 34 -[26] 0a - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadAppendText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); write(charToRaw(", World"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } -[1] "Hello2c 20 57 6f 72\n6c 64\n" - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadAppendText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); writeChar(", World", rc); res <- rawConnectionValue(rc); close(rc); res } - [1] 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 00 - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadAppendText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); writeChar(", World", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } -[1] "Hello, World" - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadWriteText# -#{ rc <- rawConnection(raw(0), "r+"); close(rc); write(charToRaw("A"), rc) } -Error in cat(x, file = file, sep = c(rep.int(sep, ncolumns - 1), "\n"), : - invalid connection - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadWriteText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); write(charToRaw(", World"), rc); res <- rawConnectionValue(rc); close(rc); res } - [1] 32 63 20 32 30 20 35 37 20 36 66 20 37 32 0a 36 63 20 36 34 0a - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadWriteText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); write(charToRaw(", World"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } -[1] "2c 20 57 6f 72\n6c 64\n" - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadWriteText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); writeChar(", World", rc); res <- rawConnectionValue(rc); close(rc); res } -[1] 2c 20 57 6f 72 6c 64 00 - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testReadWriteText# -#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); writeChar(", World", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } -[1] ", World" - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testWriteBinary#Ignored.Unknown# -#{ s <- "äöüß"; rc <- rawConnection(raw(0), "wb"); write(charToRaw(s), rc); res <- rawConnectionValue(rc); close(rc); res } - [1] 63 33 20 61 34 20 63 33 20 62 36 20 63 33 0a 62 63 20 63 33 20 39 66 0a - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testWriteBinary# -#{ zz <- rawConnection(raw(0), "wb"); x <- c("a", "this will be truncated", "abc"); nc <- c(3, 10, 3); writeChar(x, zz, nc, eos = NULL); writeChar(x, zz, eos = "\r\n"); res <- rawConnectionValue(zz); close(zz); res } - [1] 61 00 00 74 68 69 73 20 77 69 6c 6c 20 61 62 63 61 0d 0a 00 74 68 69 73 20 -[26] 77 69 6c 6c 20 62 65 20 74 72 75 6e 63 61 74 65 64 0d 0a 00 61 62 63 0d 0a -[51] 00 -Warning message: -In writeChar(x, zz, nc, eos = NULL) : - writeChar: more characters requested than are in the string - will zero-pad - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testWriteText# -#{ rc <- rawConnection(raw(0), "w"); writeChar("Hello", rc); writeChar(", World", rc); res <- rawConnectionValue(rc); close(rc); res } - [1] 48 65 6c 6c 6f 00 2c 20 57 6f 72 6c 64 00 - -##com.oracle.truffle.r.test.builtins.TestBuiltin_rawConnection.testWriteText# -#{ s <- "äöüß"; rc <- rawConnection(raw(0), "w"); writeChar(s, rc); rawConnectionValue(rc) } -[1] c3 a4 c3 b6 c3 bc c3 9f 00 - ##com.oracle.truffle.r.test.builtins.TestBuiltin_rawShift.testrawShift1# #argv <- structure(list(x = as.raw(c(0, 1, 32, 127, 128, 255, 123)), n = -1.1), .Names = c('x', 'n'));do.call('rawShift', argv) [1] 00 00 10 3f 40 7f 3d @@ -73462,6 +73399,98 @@ NULL #withRestarts({cat("<start>");invokeRestart("foo", 123L, 456L);789L},<<<NEWLINE>>> foo=list(description="my handler", handler=function(a,b) c(a,b))) <start>[1] 123 456 +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests#Ignored.Unknown# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/fifo_GnuR_example.R") } +[1] "abc" + +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_readBin.R") } + +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_readWriteBin.R") } + +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_seek.R") } + +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/rawConnection_writeBin.R") } + +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/readLines_GnuR_example.R") } +[1] "TITLE extra line" "2 3 5 7" "" "11 13 17" +[5] "123" "abc" "123" "abc def" +Warning message: +In readLines("test1") : incomplete final line found on 'test1' + +##com.oracle.truffle.r.test.library.base.TestConnections.runRSourceTests# +#{ source("mxbuild/com.oracle.truffle.r.test/bin/com/oracle/truffle/r/test/builtins/connection/R/textConnection.R") } +Read 4 items +Read 4 items + +Summary of Residuals: + + +##com.oracle.truffle.r.test.library.base.TestConnections.testEncoding# +#fin <- file('', "w+", encoding = "___inexistingCharSet___") +Error in file("", "w+", encoding = "___inexistingCharSet___") : + unsupported conversion from '___inexistingCharSet___' to '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testEncoding# +#{ wline <- 'Hellö'; fin <- file('', 'w+', encoding = 'UTF-8'); writeLines(wline, fin); seek(fin, 0); rline <- readLines(fin, 1); close(fin); c(wline, rline, wline == rline) } +[1] "Hellö" "Hellö" "TRUE" + +##com.oracle.truffle.r.test.library.base.TestConnections.testFileOpenRaw# +#{ zz <- file("gzipped_____5137528280012599068___.gz", "r", raw=T); res <- readBin(zz, raw(), 4); close(zz); res } +Error in readBin(zz, raw(), 4) : can only read from a binary connection + +##com.oracle.truffle.r.test.library.base.TestConnections.testFileSummary# +#zz <- file('', 'w+'); summary(zz); close(zz) +$description +[1] "" + +$class +[1] "file" + +$mode +[1] "w+" + +$text +[1] "text" + +$opened +[1] "opened" + +$`can read` +[1] "yes" + +$`can write` +[1] "yes" + + +##com.oracle.truffle.r.test.library.base.TestConnections.testFileSummary# +#{ zz <- file("gzipped_____5137528280012599068___.gz", "r"); res <- summary(zz); close(zz); res } +$description +[1] "gzipped_____5137528280012599068___.gz" + +$class +[1] "gzfile" + +$mode +[1] "r" + +$text +[1] "text" + +$opened +[1] "opened" + +$`can read` +[1] "yes" + +$`can write` +[1] "no" + + ##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadBin# #{ readBin(file("tmptest/com.oracle.truffle.r.test.library.base.conn/wb1", "rb"), 3) } numeric(0) @@ -73566,6 +73595,255 @@ numeric(0) #{ con<-textConnection(c("a","b","c","d")); pushBackLength(con) } [1] 0 +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadAppendText# +#{ rc <- rawConnection(raw(0), "a+"); close(rc); write(charToRaw("A"), rc) } +Error in cat(x, file = file, sep = c(rep.int(sep, ncolumns - 1), "\n"), : + invalid connection + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadAppendText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); write(charToRaw(", World"), rc); res <- rawConnectionValue(rc); close(rc); res } + [1] 48 65 6c 6c 6f 32 63 20 32 30 20 35 37 20 36 66 20 37 32 0a 36 63 20 36 34 +[26] 0a + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadAppendText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); write(charToRaw(", World"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } +[1] "Hello2c 20 57 6f 72\n6c 64\n" + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadAppendText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); writeChar(", World", rc); res <- rawConnectionValue(rc); close(rc); res } + [1] 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 00 + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadAppendText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "a+"); writeChar(", World", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } +[1] "Hello, World" + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadWriteText# +#{ rc <- rawConnection(raw(0), "r+"); close(rc); write(charToRaw("A"), rc) } +Error in cat(x, file = file, sep = c(rep.int(sep, ncolumns - 1), "\n"), : + invalid connection + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadWriteText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); write(charToRaw(", World"), rc); res <- rawConnectionValue(rc); close(rc); res } + [1] 32 63 20 32 30 20 35 37 20 36 66 20 37 32 0a 36 63 20 36 34 0a + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadWriteText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); write(charToRaw(", World"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } +[1] "2c 20 57 6f 72\n6c 64\n" + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadWriteText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); writeChar(", World", rc); res <- rawConnectionValue(rc); close(rc); res } +[1] 2c 20 57 6f 72 6c 64 00 + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawReadWriteText# +#{ rv <- charToRaw("Hello"); rc <- rawConnection(rv, "r+"); writeChar(", World", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res } +[1] ", World" + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawWriteBinary#Ignored.Unknown# +#{ s <- "äöüß"; rc <- rawConnection(raw(0), "wb"); write(charToRaw(s), rc); res <- rawConnectionValue(rc); close(rc); res } + [1] 63 33 20 61 34 20 63 33 20 62 36 20 63 33 0a 62 63 20 63 33 20 39 66 0a + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawWriteBinary# +#{ zz <- rawConnection(raw(0), "wb"); x <- c("a", "this will be truncated", "abc"); nc <- c(3, 10, 3); writeChar(x, zz, nc, eos = NULL); writeChar(x, zz, eos = "\r\n"); res <- rawConnectionValue(zz); close(zz); res } + [1] 61 00 00 74 68 69 73 20 77 69 6c 6c 20 61 62 63 61 0d 0a 00 74 68 69 73 20 +[26] 77 69 6c 6c 20 62 65 20 74 72 75 6e 63 61 74 65 64 0d 0a 00 61 62 63 0d 0a +[51] 00 +Warning message: +In writeChar(x, zz, nc, eos = NULL) : + writeChar: more characters requested than are in the string - will zero-pad + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawWriteText# +#{ rc <- rawConnection(raw(0), "w"); writeChar("Hello", rc); writeChar(", World", rc); res <- rawConnectionValue(rc); close(rc); res } + [1] 48 65 6c 6c 6f 00 2c 20 57 6f 72 6c 64 00 + +##com.oracle.truffle.r.test.library.base.TestConnections.testRawWriteText# +#{ s <- "äöüß"; rc <- rawConnection(raw(0), "w"); writeChar(s, rc); rawConnectionValue(rc) } +[1] c3 a4 c3 b6 c3 bc c3 9f 00 + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" +Warning messages: +1: In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul +2: In readLines(zz, 2, warn = T, skipNul = F) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" +Warning message: +In readLines(zz, 2, warn = T, skipNul = T) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" +Warning message: +In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" "ABC" +Warning messages: +1: In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul +2: In readLines(zz, 2, warn = T, skipNul = F) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" "ABC" +Warning message: +In readLines(zz, 2, warn = T, skipNul = T) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" "ABC" +Warning message: +In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=F); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" +Warning messages: +1: In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul +2: In readLines(zz, 2, warn = T, skipNul = F) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" +Warning message: +In readLines(zz, 2, warn = T, skipNul = T) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" +Warning message: +In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" "ABC" +Warning messages: +1: In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul +2: In readLines(zz, 2, warn = T, skipNul = F) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" "ABC" +Warning message: +In readLines(zz, 2, warn = T, skipNul = T) : + incomplete final line found on '' + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=F); close(zz); res } +[1] "abcd" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=F, skipNul=T); close(zz); res } +[1] "abcde" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=F); close(zz); res } +[1] "abcd" "ABC" +Warning message: +In readLines(zz, 2, warn = T, skipNul = F) : + line 1 appears to contain an embedded nul + +##com.oracle.truffle.r.test.library.base.TestConnections.testReadLines#Output.MayIgnoreWarningContext# +#{ zz <- file('',"w+b", blocking=T); writeBin(as.raw(c(97,98,99,100,0,101,10,65,66,67,10)), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=T, skipNul=T); close(zz); res } +[1] "abcde" "ABC" + +##com.oracle.truffle.r.test.library.base.TestConnections.testSeekTextConnection# +#{ zz <- textConnection("Hello, World!"); res <- isSeekable(zz); close(zz); res } +[1] FALSE + +##com.oracle.truffle.r.test.library.base.TestConnections.testSeekTextConnection#Output.IgnoreErrorMessage# +#{ zz <- textConnection("Hello, World!"); res <- seek(zz, 5); close(zz); res } +Error in seek.connection(zz, 5) : + seek is not relevant for text connection + +##com.oracle.truffle.r.test.library.base.TestConnections.testTextReadConnection#Output.IgnoreErrorContext# +#textConnection(NULL, 'r') +Error in textConnection(NULL, "r") : invalid 'text' argument + ##com.oracle.truffle.r.test.library.base.TestConnections.testTextReadConnection# #{ con <- textConnection(c("1", "2", "3","4")); readLines(con) } [1] "1" "2" "3" "4" @@ -73582,25 +73860,29 @@ numeric(0) #{ con <- textConnection(c("1", "2", "3","4")); readLines(con, 2); readLines(con, 2); readLines(con, 2) } character(0) -##com.oracle.truffle.r.test.library.base.TestConnections.testWriteConnection#Ignored.Unimplemented# +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# #c <- textConnection('out', 'w'); cat('testtext', file=c); isIncomplete(c); cat('testtext2\n', file=c); isIncomplete(c); close(c); out [1] TRUE [1] FALSE [1] "testtexttesttext2" -##com.oracle.truffle.r.test.library.base.TestConnections.testWriteConnection# +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# +#{ c <- textConnection(NULL, 'w'); cat('testtext\n', file=c); textConnectionValue(c) } +[1] "testtext" + +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# #{ con <- textConnection("tcval", open="w"); writeLines("a", con); tcval; close(con) } -##com.oracle.truffle.r.test.library.base.TestConnections.testWriteConnection# +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# #{ con <- textConnection("tcval", open="w"); writeLines("a", con); writeLines(c("a", "b"), con, sep="."); tcval; close(con) } -##com.oracle.truffle.r.test.library.base.TestConnections.testWriteConnection# +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# #{ con <- textConnection("tcval", open="w"); writeLines("a", con); writeLines(c("a", "b"), con, sep="."); writeLines("", con); tcval; close(con) } -##com.oracle.truffle.r.test.library.base.TestConnections.testWriteConnection# +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# #{ con <- textConnection("tcval", open="w"); writeLines("a\nb", con); tcval; close(con) } -##com.oracle.truffle.r.test.library.base.TestConnections.testWriteConnection# +##com.oracle.truffle.r.test.library.base.TestConnections.testWriteTextConnection# #{ d<-data.frame(c(1,2), c(10, 20)); buf<-character(); c<-textConnection("buf", open="w", local=T); write.table(d, c); buf } [1] "\"c.1..2.\" \"c.10..20.\"" "\"1\" 1 10" [3] "\"2\" 2 20" @@ -79641,7 +79923,7 @@ $class #{ x<-as.raw(c(7L,42L)); y<-as.data.frame(x, row.names=NULL, nm="x"); is.data.frame(y); } [1] TRUE -##com.oracle.truffle.r.test.library.base.TestSimpleDataFrames.testAsDataFrame# +##com.oracle.truffle.r.test.library.base.TestSimpleDataFrames.testAsDataFrame#Output.MayIgnoreWarningContext# #{ x<-c(7L,42L); y<-as.data.frame(x, row.names="r1", nm="x"); attributes(y); } $names [1] "x" @@ -79684,7 +79966,7 @@ $class [1] "data.frame" -##com.oracle.truffle.r.test.library.base.TestSimpleDataFrames.testAsDataFrame# +##com.oracle.truffle.r.test.library.base.TestSimpleDataFrames.testAsDataFrame#Output.MayIgnoreWarningContext# #{ x<-c(7L,42L); y<-as.data.frame(x, row.names=c("r1", "r2", "r3"), nm="x"); attributes(y); } $names [1] "x" diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_fifoConnection.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_fifoConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..106799d228cb04e6b3e3268de5ce42d7ff5470fd --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_fifoConnection.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.test.builtins; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import com.oracle.truffle.r.test.TestBase; + +// Checkstyle: stop line length check +public class TestBuiltin_fifoConnection extends TestBase { + + private static List<Path> TEMP_FIFOS = new ArrayList<>(); + + @BeforeClass + public static void setup() { + Path path = Paths.get(System.getProperty("java.io.tmpdir")); + TEMP_FIFOS.add(path.resolve("pipe3408688236")); + TEMP_FIFOS.add(path.resolve("pipe4039819292")); + } + + @Test + public void testFifoOpenInexisting() { + assertEval("capabilities(\"fifo\")"); + + Assert.assertFalse(Files.exists(TEMP_FIFOS.get(0))); + assertEval(Output.IgnoreErrorContext, Output.IgnoreWarningContext, "{ zz <- fifo(\"" + TEMP_FIFOS.get(0) + "\", \"r\", blocking = TRUE); close(zz); }"); + } + + @Test(timeout = 100) + @Ignore + public void testFifoOpenNonBlocking() { + Assert.assertFalse(Files.exists(TEMP_FIFOS.get(0))); + assertEval(Ignored.ImplementationError, "{ zz <- fifo(\"" + TEMP_FIFOS.get(0) + "\", \"r\"); close(zz); }"); + } + + @AfterClass + public static void cleanup() { + for (Path p : TEMP_FIFOS) { + try { + Files.delete(p); + } catch (IOException e) { + // ignore + } + } + } + +} diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rawConnection.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rawConnection.java deleted file mode 100644 index 33ae519955fced53b0f171c9716690dbf56c6d30..0000000000000000000000000000000000000000 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rawConnection.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.r.test.builtins; - -import org.junit.Test; - -import com.oracle.truffle.r.test.TestRBase; - -// Checkstyle: stop line length check -public class TestBuiltin_rawConnection extends TestRBase { - - @Override - protected String getTestDir() { - return "builtins/connection"; - } - - @Test - public void testReadAppendText() { - - assertEval("{ rc <- rawConnection(raw(0), \"a+\"); close(rc); write(charToRaw(\"A\"), rc) }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); writeChar(\", World\", rc); res <- rawConnectionValue(rc); close(rc); res }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); writeChar(\", World\", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); write(charToRaw(\", World\"), rc); res <- rawConnectionValue(rc); close(rc); res }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); write(charToRaw(\", World\"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); - } - - @Test - public void testReadWriteText() { - - assertEval("{ rc <- rawConnection(raw(0), \"r+\"); close(rc); write(charToRaw(\"A\"), rc) }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); writeChar(\", World\", rc); res <- rawConnectionValue(rc); close(rc); res }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); writeChar(\", World\", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); write(charToRaw(\", World\"), rc); res <- rawConnectionValue(rc); close(rc); res }"); - assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); write(charToRaw(\", World\"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); - } - - @Test - public void testWriteText() { - - assertEval("{ s <- \"äöüß\"; rc <- rawConnection(raw(0), \"w\"); writeChar(s, rc); rawConnectionValue(rc) }"); - assertEval("{ rc <- rawConnection(raw(0), \"w\"); writeChar(\"Hello\", rc); writeChar(\", World\", rc); res <- rawConnectionValue(rc); close(rc); res }"); - } - - @Test - public void testWriteBinary() { - // this test is currently ignored, since 'charToRaw' is not compliant - assertEval(Ignored.Unknown, "{ s <- \"äöüß\"; rc <- rawConnection(raw(0), \"wb\"); write(charToRaw(s), rc); res <- rawConnectionValue(rc); close(rc); res }"); - assertEval("{ zz <- rawConnection(raw(0), \"wb\"); x <- c(\"a\", \"this will be truncated\", \"abc\"); nc <- c(3, 10, 3); writeChar(x, zz, nc, eos = NULL); writeChar(x, zz, eos = \"\\r\\n\"); res <- rawConnectionValue(zz); close(zz); res }"); - } - -} diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/fifo_GnuR_example.R b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/fifo_GnuR_example.R new file mode 100644 index 0000000000000000000000000000000000000000..9ffda2392b2cf9143bc149bb054bcbdf1e8a3878 --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/fifo_GnuR_example.R @@ -0,0 +1,9 @@ +# Ignored +# This test does currently not work on Java because there is simply no way in Java for opening a UNIX named pipe non-blocking. +if(capabilities("fifo")) { + zz <- fifo("foo-fifo", "w+") + writeLines("abc", zz) + print(readLines(zz)) + close(zz) + unlink("foo-fifo") +} diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/readLines_GnuR_example.R b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/readLines_GnuR_example.R new file mode 100644 index 0000000000000000000000000000000000000000..4c6ed7c53290a25b6b54c7ed191e8b416e5c466d --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/readLines_GnuR_example.R @@ -0,0 +1,12 @@ +cat("TITLE extra line", "2 3 5 7", "", "11 13 17", file = "ex.data", sep = "\n") +r0 <- readLines("ex.data", n = -1) +unlink("ex.data") +cat("123\nabc", file = "test1") +r1 <- readLines("test1") +con <- file("test1", "r", blocking = FALSE) +r2 <- readLines(con) +cat(" def\n", file = "test1", append = TRUE) +r3 <- readLines(con) +close(con) +unlink("test1") +print(c(r0, r1, r2, r3)) \ No newline at end of file diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/textConnection.R b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/textConnection.R new file mode 100644 index 0000000000000000000000000000000000000000..7785051d50e512c1053436f50003e228a7d7fb66 --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/connection/R/textConnection.R @@ -0,0 +1,30 @@ +zz <- textConnection(LETTERS) +readLines(zz, 2) +scan(zz, "", 4) +pushBack(c("aa", "bb"), zz) +scan(zz, "", 4) +close(zz) + +zz <- textConnection("foo", "w") +writeLines(c("testit1", "testit2"), zz) +cat("testit3 ", file = zz) +isIncomplete(zz) +cat("testit4\n", file = zz) +# removed this call since we haven't implemented it yet +# isIncomplete(zz) +close(zz) +foo + +# capture R output: use part of example from help(lm) +zz <- textConnection("foo", "w") +ctl <- c(4.17, 5.58, 5.18, 6.11, 4.5, 4.61, 5.17, 4.53, 5.33, 5.14) +trt <- c(4.81, 4.17, 4.41, 3.59, 5.87, 3.83, 6.03, 4.89, 4.32, 4.69) +group <- gl(2, 10, 20, labels = c("Ctl", "Trt")) +weight <- c(ctl, trt) +sink(zz) +anova(lm.D9 <- lm(weight ~ group)) +cat("\nSummary of Residuals:\n\n") +summary(resid(lm.D9)) +sink() +close(zz) +cat(foo, sep = "\n") \ No newline at end of file diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java index b67b54e5a1ce70a0ad76d64d77ea2448218bac18..c4cfaa4a5700465d619e287b2d57966903c8d037 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java @@ -22,15 +22,24 @@ */ package com.oracle.truffle.r.test.library.base; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.zip.GZIPOutputStream; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import com.oracle.truffle.r.test.TestBase; +import com.oracle.truffle.r.test.TestRBase; -public class TestConnections extends TestBase { +// Checkstyle: stop line length check +public class TestConnections extends TestRBase { private static final class TestDir { private final Path testDirPath; @@ -44,17 +53,30 @@ public class TestConnections extends TestBase { } private static TestDir testDir; + private static Path tempFileGzip; + + @Override + protected String getTestDir() { + return "builtins/connection"; + } @BeforeClass - public static void setupTestDir() { + public static void setup() throws IOException { testDir = new TestDir(); + + // create a gzipped file + tempFileGzip = Paths.get("gzipped_____5137528280012599068___.gz"); + OutputStream gzos = new GZIPOutputStream(Files.newOutputStream(tempFileGzip, StandardOpenOption.WRITE, StandardOpenOption.CREATE)); + gzos.write("Hello, World!".getBytes()); + gzos.close(); } @AfterClass - public static void teardownTestDir() { + public static void teardown() { if (!deleteDir(testDir.testDirPath)) { System.err.println("WARNING: error deleting : " + testDir.testDirPath); } + deleteDir(tempFileGzip); } @Test @@ -84,6 +106,8 @@ public class TestConnections extends TestBase { @Test public void testTextReadConnection() { + assertEval(Output.IgnoreErrorContext, "textConnection(NULL, 'r')"); + assertEval("{ con <- textConnection(c(\"1\", \"2\", \"3\",\"4\")); readLines(con) }"); assertEval("{ con <- textConnection(c(\"1\", \"2\", \"3\",\"4\")); readLines(con, 2) }"); assertEval("{ con <- textConnection(c(\"1\", \"2\", \"3\",\"4\")); readLines(con, 2); readLines(con, 2) }"); @@ -114,13 +138,105 @@ public class TestConnections extends TestBase { } @Test - public void testWriteConnection() { + public void testWriteTextConnection() { assertEval("{ con <- textConnection(\"tcval\", open=\"w\"); writeLines(\"a\", con); tcval; close(con) }"); assertEval("{ con <- textConnection(\"tcval\", open=\"w\"); writeLines(\"a\", con); writeLines(c(\"a\", \"b\"), con, sep=\".\"); tcval; close(con) }"); assertEval("{ con <- textConnection(\"tcval\", open=\"w\"); writeLines(\"a\", con); writeLines(c(\"a\", \"b\"), con, sep=\".\"); writeLines(\"\", con); tcval; close(con) }"); assertEval("{ con <- textConnection(\"tcval\", open=\"w\"); writeLines(\"a\\nb\", con); tcval; close(con) }"); - assertEval(Ignored.Unimplemented, "c <- textConnection('out', 'w'); cat('testtext', file=c); isIncomplete(c); cat('testtext2\\n', file=c); isIncomplete(c); close(c); out"); + assertEval("c <- textConnection('out', 'w'); cat('testtext', file=c); isIncomplete(c); cat('testtext2\\n', file=c); isIncomplete(c); close(c); out"); + + // anonymous connection + assertEval("{ c <- textConnection(NULL, 'w'); cat('testtext\\n', file=c); textConnectionValue(c) }"); assertEval("{ d<-data.frame(c(1,2), c(10, 20)); buf<-character(); c<-textConnection(\"buf\", open=\"w\", local=T); write.table(d, c); buf }"); } + + @Test + public void testSeekTextConnection() { + assertEval("{ zz <- textConnection(\"Hello, World!\"); res <- isSeekable(zz); close(zz); res }"); + assertEval(Output.IgnoreErrorMessage, "{ zz <- textConnection(\"Hello, World!\"); res <- seek(zz, 5); close(zz); res }"); + } + + @Test + public void testFileSummary() { + Assert.assertTrue("Could not create required temp file for test.", Files.exists(tempFileGzip)); + assertEval("{ zz <- file(\"" + tempFileGzip + "\", \"r\"); res <- summary(zz); close(zz); res }"); + + assertEval("zz <- file('', 'w+'); summary(zz); close(zz)"); + } + + @Test + public void testFileOpenRaw() { + Assert.assertTrue("Could not create required temp file for test.", Files.exists(tempFileGzip)); + assertEval("{ zz <- file(\"" + tempFileGzip + "\", \"r\", raw=T); res <- readBin(zz, raw(), 4); close(zz); res }"); + } + + @Test + public void testEncoding() { + // use inexisting charset + assertEval("fin <- file('', \"w+\", encoding = \"___inexistingCharSet___\")"); + + // write UTF-8 file + assertEval("{ wline <- 'Hellö'; fin <- file('', 'w+', encoding = 'UTF-8'); writeLines(wline, fin); seek(fin, 0); rline <- readLines(fin, 1); close(fin); c(wline, rline, wline == rline) }"); + } + + @Test + public void testReadLines() { + // one line containing '\0' + final String lineWithNul = "c(97,98,99,100,0,101,10)"; + + // two lines, first containing '\0' + final String twoLinesOneNul = "c(97,98,99,100,0,101,10,65,66,67,10)"; + + // one line containing '\0' and imcomplete + final String lineWithNulIncomp = "c(97,98,99,100,0,101)"; + + // two lines, first containing '\0', second line incomplete + final String twoLinesOneNulIncomp = "c(97,98,99,100,0,101,10,65,66,67)"; + + assertEval(Output.MayIgnoreWarningContext, TestBase.template( + "{ zz <- file('',\"w+b\", blocking=%0); writeBin(as.raw(%1), zz, useBytes=T); seek(zz, 0); res <- readLines(zz, 2, warn=%2, skipNul=%3); close(zz); res }", + LVAL, arr(lineWithNul, twoLinesOneNul, lineWithNulIncomp, twoLinesOneNulIncomp), LVAL, LVAL)); + } + + @Test + public void testRawReadAppendText() { + + assertEval("{ rc <- rawConnection(raw(0), \"a+\"); close(rc); write(charToRaw(\"A\"), rc) }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); writeChar(\", World\", rc); res <- rawConnectionValue(rc); close(rc); res }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); writeChar(\", World\", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); write(charToRaw(\", World\"), rc); res <- rawConnectionValue(rc); close(rc); res }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"a+\"); write(charToRaw(\", World\"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); + } + + @Test + public void testRawReadWriteText() { + + assertEval("{ rc <- rawConnection(raw(0), \"r+\"); close(rc); write(charToRaw(\"A\"), rc) }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); writeChar(\", World\", rc); res <- rawConnectionValue(rc); close(rc); res }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); writeChar(\", World\", rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); write(charToRaw(\", World\"), rc); res <- rawConnectionValue(rc); close(rc); res }"); + assertEval("{ rv <- charToRaw(\"Hello\"); rc <- rawConnection(rv, \"r+\"); write(charToRaw(\", World\"), rc); res <- rawToChar(rawConnectionValue(rc)); close(rc); res }"); + } + + @Test + public void testRawWriteText() { + + assertEval("{ s <- \"äöüß\"; rc <- rawConnection(raw(0), \"w\"); writeChar(s, rc); rawConnectionValue(rc) }"); + assertEval("{ rc <- rawConnection(raw(0), \"w\"); writeChar(\"Hello\", rc); writeChar(\", World\", rc); res <- rawConnectionValue(rc); close(rc); res }"); + } + + @Test + public void testRawWriteBinary() { + + // this test is currently ignored, since 'charToRaw' is not compliant + assertEval(Ignored.Unknown, "{ s <- \"äöüß\"; rc <- rawConnection(raw(0), \"wb\"); write(charToRaw(s), rc); res <- rawConnectionValue(rc); close(rc); res }"); + assertEval("{ zz <- rawConnection(raw(0), \"wb\"); x <- c(\"a\", \"this will be truncated\", \"abc\"); nc <- c(3, 10, 3); writeChar(x, zz, nc, eos = NULL); writeChar(x, zz, eos = \"\\r\\n\"); res <- rawConnectionValue(zz); close(zz); res }"); + } + + private static final String[] LVAL = arr("T", "F"); + + private static String[] arr(String... args) { + return args; + } }