Skip to content

Commit

Permalink
Showing 5 changed files with 3,105 additions and 1 deletion.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/util/EmptyFileResource.java
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

import java.nio.channels.Channel;

class EmptyFileResource implements FileResource {
public class EmptyFileResource implements FileResource {
// All empty resources are the same and immutable, so may as well
// cache the instance
private static final EmptyFileResource INSTANCE = new EmptyFileResource();
356 changes: 356 additions & 0 deletions core/src/main/java/org/jruby/util/io/CRLFStreamWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
package org.jruby.util.io;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import org.jcodings.Encoding;
import org.jcodings.specific.UTF16BEEncoding;
import org.jcodings.specific.UTF16LEEncoding;
import org.jruby.Ruby;
import org.jruby.platform.Platform;
import org.jruby.util.ByteList;

/**
* Wrapper around Stream that packs and unpacks LF <=> CRLF.
* @author nicksieger
*/
public class CRLFStreamWrapper implements Stream {
private final Stream stream;
private final boolean isWindows;
private boolean binmode = false;
private static final int CR = 13;
private static final int LF = 10;

public CRLFStreamWrapper(Stream stream) {
this.stream = stream;
// To differentiate between textmode and windows in how we handle crlf.
this.isWindows = Platform.IS_WINDOWS;
}

public ChannelDescriptor getDescriptor() {
return stream.getDescriptor();
}

public void clearerr() {
stream.clearerr();
}

@Override
public ByteBuffer getBuffer() {
return stream.getBuffer();
}

public ModeFlags getModes() {
return stream.getModes();
}

public void setModes(ModeFlags modes) {
stream.setModes(modes);
}

public boolean isSync() {
return stream.isSync();
}

public void setSync(boolean sync) {
stream.setSync(sync);
}

@Override
public int bufferedAvailable() {
return stream.bufferedAvailable();
}

public void setBinmode() {
binmode = true;
stream.setBinmode();
}

public boolean isBinmode() {
return binmode;
}

public boolean isAutoclose() {
return stream.isAutoclose();
}

public void setAutoclose(boolean autoclose) {
stream.setAutoclose(autoclose);
}

public ByteList fgets(ByteList separatorString) throws IOException, BadDescriptorException, EOFException {
return convertCRLFToLF(stream.fgets(separatorString));
}

public ByteList readall() throws IOException, BadDescriptorException, EOFException {
return convertCRLFToLF(stream.readall());
}

public int getline(ByteList dst, byte terminator) throws IOException, BadDescriptorException {
if (binmode) {
return stream.getline(dst, terminator);
}

ByteList intermediate = new ByteList();
int result = stream.getline(intermediate, terminator);
convertCRLFToLF(intermediate, dst);
return result;
}

public int getline(ByteList dst, byte terminator, long limit) throws IOException, BadDescriptorException {
if (binmode) {
return stream.getline(dst, terminator, limit);
}

ByteList intermediate = new ByteList();
int result = stream.getline(intermediate, terminator, limit);
convertCRLFToLF(intermediate, dst);
return result;
}

public ByteList fread(int number) throws IOException, BadDescriptorException, EOFException {
if (number == 0) {
if (stream.feof()) {
return null;
} else {
return new ByteList(0);
}
}
boolean eof = false;
ByteList bl = new ByteList(number > ChannelStream.BUFSIZE ? ChannelStream.BUFSIZE : number);
for (int i = 0; i < number; i++) {
int c = fgetc();
if (c == -1) {
eof = true;
break;
}
bl.append(c);
}
if (eof && bl.length() == 0) {
return null;
}
return bl;
}

public int fwrite(ByteList string) throws IOException, BadDescriptorException {
if (isWindows) return stream.fwrite(convertLFToCRLF(string));

return stream.fwrite(convertCRLFToLF(string));
}

public int fgetc() throws IOException, BadDescriptorException, EOFException {
int c = stream.fgetc();
if (!binmode && c == CR) {
c = stream.fgetc();
if (c != LF) {
stream.ungetc(c);
return CR;
}
}
return c;
}

public int ungetc(int c) {
return stream.ungetc(c);
}

public void fputc(int c) throws IOException, BadDescriptorException {
if (!binmode && c == LF) {
stream.fputc(CR);
}
stream.fputc(c);
}

public ByteList read(int number) throws IOException, BadDescriptorException, EOFException {
return convertCRLFToLF(stream.read(number));
}

public void fclose() throws IOException, BadDescriptorException {
stream.fclose();
}

public int fflush() throws IOException, BadDescriptorException {
return stream.fflush();
}

public void sync() throws IOException, BadDescriptorException {
stream.sync();
}

public boolean feof() throws IOException, BadDescriptorException {
return stream.feof();
}

public long fgetpos() throws IOException, PipeException, BadDescriptorException, InvalidValueException {
return stream.fgetpos();
}

public void lseek(long offset, int type) throws IOException, InvalidValueException, PipeException, BadDescriptorException {
stream.lseek(offset, type);
}

public void ftruncate(long newLength) throws IOException, PipeException, InvalidValueException, BadDescriptorException {
stream.ftruncate(newLength);
}

public int ready() throws IOException {
return stream.ready();
}

public void waitUntilReady() throws IOException, InterruptedException {
stream.waitUntilReady();
}

public boolean readDataBuffered() {
return stream.readDataBuffered();
}

public boolean writeDataBuffered() {
return stream.writeDataBuffered();
}

public InputStream newInputStream() {
return stream.newInputStream();
}

public OutputStream newOutputStream() {
return stream.newOutputStream();
}

public boolean isBlocking() {
return stream.isBlocking();
}

public void setBlocking(boolean blocking) throws IOException {
stream.setBlocking(blocking);
}

public void freopen(Ruby runtime, String path, ModeFlags modes) throws DirectoryAsFileException, IOException, InvalidValueException, PipeException, BadDescriptorException {
stream.freopen(runtime, path, modes);
}

private ByteList convertCRLFToLF(ByteList input) {
if (input == null || binmode) return input;

ByteList result = new ByteList();
convertCRLFToLF(input, result);
return result;
}

// FIXME: Horrific hack until we properly setup transcoding support of cr/lf logic in 1.9 proper. This class
// is going away in 9k and the LE/BE logic is never used by 1.8 support.

// I could not find any way in MRI to exercise this logic....endless needs
// binmode set (which obviously would not work here). Leaving it for now
// since I will likely be either doubling down on new knowledge for 1.7.2
// or ripping all this out when we have real transcoding logic ported
// properly
// private int skipCROfLF(ByteList src, int i, int c) {
// Encoding encoding = src.getEncoding();
// int length = src.length();
//
// if (encoding == UTF16BEEncoding.INSTANCE) {
// if (i + 3 < length && c == 0 && src.get(i + 1) == CR &&
// src.get(i + 2) == 0 && src.get(i + 3) == LF) {
// return i + 1;
// }
// } else if (encoding == UTF16LEEncoding.INSTANCE) {
// if (i + 3 < length && c == CR && src.get(i + 1) == 0 &&
// src.get(i + 2) == LF && src.get(i + 3) == 0) {
// return i + 1;
// }
// } else if (c == CR && i + 1 < length && src.get(i + 1) == LF) {
// return i;
// }
//
// return -1;
// }
//
// private void convertCRLFToLF(ByteList src, ByteList dst) {
// for (int i = 0; i < src.length(); i++) {
// int b = src.get(i);
// int j = skipCROfLF(src, i, b);
// if (j != -1) i = j;
//
// dst.append(b);
// }
// }


private void convertCRLFToLF(ByteList src, ByteList dst) {
for (int i = 0; i < src.length(); i++) {
int b = src.get(i);
if (b == CR && i + 1 < src.length() && src.get(i + 1) == LF) {
continue;
}
dst.append(b);
}
}

final byte[] CRBYTES = new byte[] { CR };
final byte[] CRLEBYTES = new byte[] { CR, 0};
final byte[] CRBEBYTES = new byte[] { 0, CR };

private byte[] crBytes(Encoding encoding) {
if (encoding == UTF16BEEncoding.INSTANCE) return CRBEBYTES;
if (encoding == UTF16LEEncoding.INSTANCE) return CRLEBYTES;

return CRBYTES;
}

final byte[] LFBYTES = new byte[] { LF };
final byte[] LFLEBYTES = new byte[] { LF, 0 };
final byte[] LFBEBYTES = new byte[] { 0, LF };

private byte[] lfBytes(Encoding encoding) {
if (encoding == UTF16BEEncoding.INSTANCE) return LFBEBYTES;
if (encoding == UTF16LEEncoding.INSTANCE) return LFLEBYTES;

return LFBYTES;
}

private ByteList convertLFToCRLF(ByteList bs) {
if (bs == null || binmode) return bs;

byte[] crBytes = crBytes(bs.getEncoding());
byte[] lfBytes = lfBytes(bs.getEncoding());

int p = bs.getBegin();
int end = p + bs.getRealSize();
byte[]bytes = bs.getUnsafeBytes();
Encoding enc = bs.getEncoding();

ByteList result = new ByteList();
int lastWrittenIndex = p;
while (p < end) {
int c = enc.mbcToCode(bytes, p, end);
int cLength = enc.codeToMbcLength(c);

if (c == LF) {
result.append(bytes, lastWrittenIndex, p - lastWrittenIndex);
result.append(crBytes);
result.append(lfBytes);
lastWrittenIndex = p + cLength;
}

p += cLength;
}

if (lastWrittenIndex < end) {
result.append(bytes, lastWrittenIndex, end - lastWrittenIndex);
}

return result;
}

public Channel getChannel() {
return stream.getChannel();
}

public final int refillBuffer() throws IOException {
return stream.refillBuffer();
}
}
861 changes: 861 additions & 0 deletions core/src/main/java/org/jruby/util/io/ChannelDescriptor.java

Large diffs are not rendered by default.

1,699 changes: 1,699 additions & 0 deletions core/src/main/java/org/jruby/util/io/ChannelStream.java

Large diffs are not rendered by default.

188 changes: 188 additions & 0 deletions core/src/main/java/org/jruby/util/io/Stream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2008-2009 The JRuby Community <www.jruby.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.util.io;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import org.jruby.util.ByteList;
import org.jruby.Ruby;

/**
*/
public interface Stream {
static final int SEEK_SET = 0;
static final int SEEK_CUR = 1;
static final int SEEK_END = 2;

// We use a highly uncommon string to represent the paragraph delimiter (100% soln not worth it)
static final ByteList PARAGRAPH_DELIMETER = ByteList.create("PARAGRPH_DELIM_MRK_ER");

static final ByteList PARAGRAPH_SEPARATOR = ByteList.create("\n\n");

ChannelDescriptor getDescriptor();

void clearerr();

ByteBuffer getBuffer();

ModeFlags getModes();

void setModes(ModeFlags modeFlags);

boolean isSync();

void setSync(boolean sync);

int bufferedAvailable();

int refillBuffer() throws IOException;

ByteList fgets(ByteList separatorString) throws IOException, BadDescriptorException, EOFException;
ByteList readall() throws IOException, BadDescriptorException, EOFException;


/**
* Read all bytes up to and including a terminator byte.
*
* <p>If the terminator byte is found, it will be the last byte in the output buffer.</p>
*
* @param dst The output buffer.
* @param terminator The byte to terminate reading.
* @return The number of bytes read, or -1 if EOF is reached.
*
* @throws java.io.IOException
* @throws org.jruby.util.io.BadDescriptorException
*/
int getline(ByteList dst, byte terminator) throws IOException, BadDescriptorException;

/**
* Reads all bytes up to and including a terminator byte or until limit is reached.
*
* <p>If the terminator byte is found, it will be the last byte in the output buffer.</p>
*
* @param dst The output buffer.
* @param terminator The byte to terminate reading.
* @param limit the number of bytes to read unless EOF or terminator is found
* @return The number of bytes read, or -1 if EOF is reached.
*
* @throws java.io.IOException
* @throws org.jruby.util.io.BadDescriptorException
*/
int getline(ByteList dst, byte terminator, long limit) throws IOException, BadDescriptorException;

// TODO: We overflow on large files...We could increase to long to limit
// this, but then the impl gets more involved since java io APIs based on
// int (means we have to chunk up a long into a series of int ops).

ByteList fread(int number) throws IOException, BadDescriptorException, EOFException;
int fwrite(ByteList string) throws IOException, BadDescriptorException;

int fgetc() throws IOException, BadDescriptorException, EOFException;
int ungetc(int c);
void fputc(int c) throws IOException, BadDescriptorException;

ByteList read(int number) throws IOException, BadDescriptorException, EOFException;

void fclose() throws IOException, BadDescriptorException;
int fflush() throws IOException, BadDescriptorException;

/**
* <p>Flush and sync all writes to the filesystem.</p>
*
* @throws IOException if the sync does not work
*/
void sync() throws IOException, BadDescriptorException;

/**
* <p>Return true when at end of file (EOF).</p>
*
* @return true if at EOF; false otherwise
* @throws IOException
* @throws BadDescriptorException
*/
boolean feof() throws IOException, BadDescriptorException;

/**
* <p>Get the current position within the file associated with this
* handler.</p>
*
* @return the current position in the file.
* @throws IOException
* @throws PipeException ESPIPE (illegal seek) when not a file
*
*/
long fgetpos() throws IOException, PipeException, BadDescriptorException, InvalidValueException;

/**
* <p>Perform a seek based on pos(). </p>
* @throws IOException
* @throws PipeException
* @throws InvalidValueException
*/
void lseek(long offset, int type) throws IOException, InvalidValueException, PipeException, BadDescriptorException;
void ftruncate(long newLength) throws IOException, PipeException,
InvalidValueException, BadDescriptorException;

/**
* Implement IO#ready? as per io/wait in MRI.
* returns non-nil if input available without blocking, or nil.
*/
int ready() throws IOException;

/**
* Implement IO#wait as per io/wait in MRI.
* waits until input available or timed out and returns self, or nil when EOF reached.
*
* The default implementation loops while ready returns 0.
*/
void waitUntilReady() throws IOException, InterruptedException;

boolean readDataBuffered();
boolean writeDataBuffered();

InputStream newInputStream();

OutputStream newOutputStream();

boolean isBlocking();

void setBlocking(boolean blocking) throws IOException;

void freopen(Ruby runtime, String path, ModeFlags modes) throws DirectoryAsFileException, IOException, InvalidValueException, PipeException, BadDescriptorException;

void setBinmode();
boolean isBinmode();
Channel getChannel();

boolean isAutoclose();
void setAutoclose(boolean autoclose);
}

0 comments on commit 6884f63

Please sign in to comment.