public EncodingService(Ruby runtime) { this.runtime = runtime; encodings = EncodingDB.getEncodings(); aliases = EncodingDB.getAliases(); ascii8bit = encodings.get("ASCII-8BIT".getBytes()).getEncoding(); Charset javaDefaultCharset = Charset.defaultCharset(); ByteList javaDefaultBL = new ByteList(javaDefaultCharset.name().getBytes()); Entry javaDefaultEntry = findEncodingOrAliasEntry(javaDefaultBL); javaDefault = javaDefaultEntry == null ? ascii8bit : javaDefaultEntry.getEncoding(); encodingList = new IRubyObject[encodings.size()]; if (runtime.is1_9()) { RubyEncoding.createEncodingClass(runtime); RubyConverter.createConverterClass(runtime); defineEncodings(); defineAliases(); // External should always have a value, but Encoding.external_encoding{,=} will lazily setup String encoding = runtime.getInstanceConfig().getExternalEncoding(); if (encoding != null && !encoding.equals("")) { Encoding loadedEncoding = loadEncoding(ByteList.create(encoding)); if (loadedEncoding == null) throw new MainExitException(1, "unknown encoding name - " + encoding); runtime.setDefaultExternalEncoding(loadedEncoding); } else { Encoding consoleEncoding = getConsoleEncoding(); Encoding availableEncoding = consoleEncoding == null ? getLocaleEncoding() : consoleEncoding; runtime.setDefaultExternalEncoding(availableEncoding); } encoding = runtime.getInstanceConfig().getInternalEncoding(); if (encoding != null && !encoding.equals("")) { Encoding loadedEncoding = loadEncoding(ByteList.create(encoding)); if (loadedEncoding == null) throw new MainExitException(1, "unknown encoding name - " + encoding); runtime.setDefaultInternalEncoding(loadedEncoding); } } }
public Node toYamlNode(Representer representer) throws IOException { String str = data.toString(); if (str.equals("Infinity")) { str = ".inf"; } else if (str.equals("-Infinity")) { str = "-.inf"; } else if (str.equals("NaN")) { str = ".nan"; } return representer.scalar(taguri(), ByteList.create(str), (char) 0); }
public Node toYamlNode(final Representer representer) throws IOException { final Calendar c = Calendar.getInstance(); c.setTime(data); String out = null; if (c.get(Calendar.MILLISECOND) != 0) { out = dateOutputUsec.format(data); } else { out = dateOutput.format(data); } out = out.substring(0, 23) + ":" + out.substring(23); return representer.scalar(taguri(), ByteList.create(out), (char) 0); }
public IRubyObject getDefaultExternal() { IRubyObject defaultExternal = convertEncodingToRubyEncoding(runtime.getDefaultExternalEncoding()); if (defaultExternal.isNil()) { // TODO: MRI seems to default blindly to US-ASCII and we were using Charset default from // Java...which is right? ByteList encodingName = ByteList.create("US-ASCII"); Encoding encoding = runtime.getEncodingService().loadEncoding(encodingName); runtime.setDefaultExternalEncoding(encoding); defaultExternal = convertEncodingToRubyEncoding(encoding); } return defaultExternal; }
/** * Since Java 1.6, class {@link java.io.Console} is available. But the encoding or codepage of the * underlying connected console is currently private. Had to use Reflection to get it. * * @return console codepage */ private Encoding getConsoleEncoding() { if (!Platform.IS_WINDOWS) return null; Encoding consoleEncoding = null; try { Console console = System.console(); if (console != null) { final String CONSOLE_CHARSET = "cs"; Field fcs = Console.class.getDeclaredField(CONSOLE_CHARSET); fcs.setAccessible(true); Charset cs = (Charset) fcs.get(console); consoleEncoding = loadEncoding(ByteList.create(cs.name())); } } catch (Throwable e) { // to cover both Exceptions and Errors // just fall back on local encoding above } return consoleEncoding; }
public Node toYamlNode(Representer representer) throws IOException { return representer.scalar(taguri(), ByteList.create(data.toString()), (char) 0); }
@JRubyClass(name = "StringIO") public class RubyStringIO extends RubyObject { private static ObjectAllocator STRINGIO_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new RubyStringIO(runtime, klass); } }; public static RubyClass createStringIOClass(final Ruby runtime) { RubyClass stringIOClass = runtime.defineClass("StringIO", runtime.fastGetClass("Data"), STRINGIO_ALLOCATOR); stringIOClass.defineAnnotatedMethods(RubyStringIO.class); stringIOClass.includeModule(runtime.getEnumerable()); if (runtime.getObject().isConstantDefined("Java")) { stringIOClass.defineAnnotatedMethods(IOJavaAddons.AnyIO.class); } return stringIOClass; } @JRubyMethod(name = "open", optional = 2, frame = true, meta = true) public static IRubyObject open( ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { RubyStringIO strio = (RubyStringIO) ((RubyClass) recv).newInstance(context, args, Block.NULL_BLOCK); IRubyObject val = strio; if (block.isGiven()) { try { val = block.yield(context, strio); } finally { strio.doFinalize(); } } return val; } protected RubyStringIO(Ruby runtime, RubyClass klass) { super(runtime, klass); } private long pos = 0L; private int lineno = 0; private boolean eof = false; /** * ATTN: the value of internal might be reset to null (during StringIO.open with block), so watch * out for that. */ private RubyString internal; // Has read/write been closed or is it still open for business private boolean closedRead = false; private boolean closedWrite = false; // Support IO modes that this object was opened with ModeFlags modes; private void initializeModes(Object modeArgument) { try { if (modeArgument == null) { modes = new ModeFlags(RubyIO.getIOModesIntFromString(getRuntime(), "r+")); } else if (modeArgument instanceof Long) { modes = new ModeFlags(((Long) modeArgument).longValue()); } else { modes = new ModeFlags(RubyIO.getIOModesIntFromString(getRuntime(), (String) modeArgument)); } } catch (InvalidValueException e) { throw getRuntime().newErrnoEINVALError(); } setupModes(); } @JRubyMethod(name = "initialize", optional = 2, frame = true, visibility = Visibility.PRIVATE) public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) { Object modeArgument = null; switch (args.length) { case 0: internal = RubyString.newEmptyString(getRuntime()); modeArgument = "r+"; break; case 1: internal = args[0].convertToString(); modeArgument = internal.isFrozen() ? "r" : "r+"; break; case 2: internal = args[0].convertToString(); if (args[1] instanceof RubyFixnum) { modeArgument = RubyFixnum.fix2long(args[1]); } else { modeArgument = args[1].convertToString().toString(); } break; } initializeModes(modeArgument); if (modes.isWritable() && internal.isFrozen()) { throw getRuntime().newErrnoEACCESError("Permission denied"); } if (modes.isTruncate()) { internal.modifyCheck(); internal.empty(); } return this; } @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject initialize_copy(IRubyObject other) { RubyStringIO otherIO = (RubyStringIO) TypeConverter.convertToType(other, getRuntime().fastGetClass("StringIO"), "to_strio"); if (this == otherIO) { return this; } pos = otherIO.pos; lineno = otherIO.lineno; eof = otherIO.eof; closedRead = otherIO.closedRead; closedWrite = otherIO.closedWrite; internal = otherIO.internal; modes = otherIO.modes; if (otherIO.isTaint()) { setTaint(true); } return this; } @JRubyMethod(name = "<<", required = 1) public IRubyObject append(ThreadContext context, IRubyObject arg) { writeInternal(context, arg); return this; } @JRubyMethod(name = "binmode") public IRubyObject binmode() { return this; } @JRubyMethod(name = "close", frame = true) public IRubyObject close() { checkInitialized(); checkOpen(); closedRead = true; closedWrite = true; return getRuntime().getNil(); } private void doFinalize() { closedRead = true; closedWrite = true; internal = null; } @JRubyMethod(name = "closed?") public IRubyObject closed_p() { checkInitialized(); return getRuntime().newBoolean(closedRead && closedWrite); } @JRubyMethod(name = "close_read") public IRubyObject close_read() { checkReadable(); closedRead = true; return getRuntime().getNil(); } @JRubyMethod(name = "closed_read?") public IRubyObject closed_read_p() { checkInitialized(); return getRuntime().newBoolean(closedRead); } @JRubyMethod(name = "close_write") public IRubyObject close_write() { checkWritable(); closedWrite = true; return getRuntime().getNil(); } @JRubyMethod(name = "closed_write?") public IRubyObject closed_write_p() { checkInitialized(); return getRuntime().newBoolean(closedWrite); } @JRubyMethod(name = "each", optional = 1, frame = true, writes = FrameField.LASTLINE) public IRubyObject each(ThreadContext context, IRubyObject[] args, Block block) { IRubyObject line = getsOnly(context, args); while (!line.isNil()) { block.yield(context, line); line = getsOnly(context, args); } return this; } @JRubyMethod(name = "each_byte", frame = true) public IRubyObject each_byte(ThreadContext context, Block block) { checkReadable(); Ruby runtime = context.getRuntime(); ByteList bytes = internal.getByteList(); // Check the length every iteration, since // the block can modify this string. while (pos < bytes.length()) { block.yield(context, runtime.newFixnum(bytes.get((int) pos++) & 0xFF)); } return runtime.getNil(); } @JRubyMethod(name = "each_line", optional = 1, frame = true) public IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block block) { return each(context, args, block); } @JRubyMethod(name = {"eof", "eof?"}) public IRubyObject eof() { return getRuntime().newBoolean(isEOF()); } private boolean isEOF() { return (pos >= internal.getByteList().length()) || eof; } @JRubyMethod(name = "fcntl") public IRubyObject fcntl() { throw getRuntime().newNotImplementedError("fcntl not implemented"); } @JRubyMethod(name = "fileno") public IRubyObject fileno() { return getRuntime().getNil(); } @JRubyMethod(name = "flush") public IRubyObject flush() { return this; } @JRubyMethod(name = "fsync") public IRubyObject fsync() { return RubyFixnum.zero(getRuntime()); } @JRubyMethod(name = "getc") public IRubyObject getc() { checkReadable(); if (pos >= internal.getByteList().length()) { return getRuntime().getNil(); } return getRuntime().newFixnum(internal.getByteList().get((int) pos++) & 0xFF); } private IRubyObject internalGets(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.getRuntime(); if (pos < internal.getByteList().realSize && !eof) { boolean isParagraph = false; ByteList sep; if (args.length > 0) { if (args[0].isNil()) { ByteList buf = internal .getByteList() .makeShared((int) pos, internal.getByteList().realSize - (int) pos); pos += buf.realSize; return RubyString.newString(runtime, buf); } sep = args[0].convertToString().getByteList(); if (sep.realSize == 0) { isParagraph = true; sep = Stream.PARAGRAPH_SEPARATOR; } } else { sep = ((RubyString) runtime.getGlobalVariables().get("$/")).getByteList(); } ByteList ss = internal.getByteList(); if (isParagraph) { swallowLF(ss); if (pos == ss.realSize) { return runtime.getNil(); } } int ix = ss.indexOf(sep, (int) pos); ByteList add; if (-1 == ix) { ix = internal.getByteList().realSize; add = ByteList.EMPTY_BYTELIST; } else { add = isParagraph ? NEWLINE : sep; } ByteList line = new ByteList(ix - (int) pos + add.length()); line.append(internal.getByteList(), (int) pos, ix - (int) pos); line.append(add); pos = ix + add.realSize; lineno++; return RubyString.newString(runtime, line); } return runtime.getNil(); } private void swallowLF(ByteList list) { while (pos < list.realSize) { if (list.get((int) pos) == '\n') { pos++; } else { break; } } } @JRubyMethod(name = "gets", optional = 1, writes = FrameField.LASTLINE) public IRubyObject gets(ThreadContext context, IRubyObject[] args) { IRubyObject result = getsOnly(context, args); context.getCurrentScope().setLastLine(result); return result; } public IRubyObject getsOnly(ThreadContext context, IRubyObject[] args) { checkReadable(); return internalGets(context, args); } @JRubyMethod(name = {"tty?", "isatty"}) public IRubyObject isatty() { return getRuntime().getFalse(); } @JRubyMethod(name = {"length", "size"}) public IRubyObject length() { checkFinalized(); return getRuntime().newFixnum(internal.getByteList().length()); } @JRubyMethod(name = "lineno") public IRubyObject lineno() { return getRuntime().newFixnum(lineno); } @JRubyMethod(name = "lineno=", required = 1) public IRubyObject set_lineno(IRubyObject arg) { lineno = RubyNumeric.fix2int(arg); return getRuntime().getNil(); } @JRubyMethod(name = "path") public IRubyObject path() { return getRuntime().getNil(); } @JRubyMethod(name = "pid") public IRubyObject pid() { return getRuntime().getNil(); } @JRubyMethod(name = {"pos", "tell"}) public IRubyObject pos() { return getRuntime().newFixnum(pos); } @JRubyMethod(name = "pos=", required = 1) public IRubyObject set_pos(IRubyObject arg) { pos = RubyNumeric.fix2int(arg); if (pos < 0) { throw getRuntime().newErrnoEINVALError("Invalid argument"); } return getRuntime().getNil(); } @JRubyMethod(name = "print", rest = true) public IRubyObject print(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.getRuntime(); if (args.length != 0) { for (int i = 0, j = args.length; i < j; i++) { append(context, args[i]); } } else { IRubyObject arg = runtime.getGlobalVariables().get("$_"); append(context, arg.isNil() ? runtime.newString("nil") : arg); } IRubyObject sep = runtime.getGlobalVariables().get("$\\"); if (!sep.isNil()) { append(context, sep); } return getRuntime().getNil(); } @JRubyMethod(name = "printf", required = 1, rest = true) public IRubyObject printf(ThreadContext context, IRubyObject[] args) { append(context, RubyKernel.sprintf(context, this, args)); return getRuntime().getNil(); } @JRubyMethod(name = "putc", required = 1) public IRubyObject putc(IRubyObject obj) { checkWritable(); byte c = RubyNumeric.num2chr(obj); checkFrozen(); internal.modify(); ByteList bytes = internal.getByteList(); if (modes.isAppendable()) { pos = bytes.length(); bytes.append(c); } else { if (pos >= bytes.length()) { bytes.length((int) pos + 1); } bytes.set((int) pos, c); pos++; } return obj; } public static final ByteList NEWLINE = ByteList.create("\n"); @JRubyMethod(name = "puts", rest = true) public IRubyObject puts(ThreadContext context, IRubyObject[] args) { checkWritable(); // FIXME: the code below is a copy of RubyIO.puts, // and we should avoid copy-paste. if (args.length == 0) { callMethod(context, "write", RubyString.newStringShared(getRuntime(), NEWLINE)); return getRuntime().getNil(); } for (int i = 0; i < args.length; i++) { RubyString line; if (args[i].isNil()) { line = getRuntime().newString("nil"); } else { IRubyObject tmp = args[i].checkArrayType(); if (!tmp.isNil()) { RubyArray arr = (RubyArray) tmp; if (getRuntime().isInspecting(arr)) { line = getRuntime().newString("[...]"); } else { inspectPuts(context, arr); continue; } } else { if (args[i] instanceof RubyString) { line = (RubyString) args[i]; } else { line = args[i].asString(); } } } callMethod(context, "write", line); if (!line.getByteList().endsWith(NEWLINE)) { callMethod(context, "write", RubyString.newStringShared(getRuntime(), NEWLINE)); } } return getRuntime().getNil(); } private IRubyObject inspectPuts(ThreadContext context, RubyArray array) { try { getRuntime().registerInspecting(array); return puts(context, array.toJavaArray()); } finally { getRuntime().unregisterInspecting(array); } } @SuppressWarnings("fallthrough") @JRubyMethod(name = "read", optional = 2) public IRubyObject read(IRubyObject[] args) { checkReadable(); ByteList buf = null; int length = 0; int oldLength = 0; RubyString originalString = null; switch (args.length) { case 2: originalString = args[1].convertToString(); // must let original string know we're modifying, so shared buffers aren't damaged originalString.modify(); buf = originalString.getByteList(); case 1: if (!args[0].isNil()) { length = RubyNumeric.fix2int(args[0]); oldLength = length; if (length < 0) { throw getRuntime().newArgumentError("negative length " + length + " given"); } if (length > 0 && pos >= internal.getByteList().length()) { eof = true; if (buf != null) buf.realSize = 0; return getRuntime().getNil(); } else if (eof) { if (buf != null) buf.realSize = 0; return getRuntime().getNil(); } break; } case 0: oldLength = -1; length = internal.getByteList().length(); if (length <= pos) { eof = true; if (buf == null) { buf = new ByteList(); } else { buf.realSize = 0; } return getRuntime().newString(buf); } else { length -= pos; } break; default: getRuntime().newArgumentError(args.length, 0); } if (buf == null) { int internalLength = internal.getByteList().length(); if (internalLength > 0) { if (internalLength >= pos + length) { buf = new ByteList(internal.getByteList(), (int) pos, length); } else { int rest = (int) (internal.getByteList().length() - pos); if (length > rest) length = rest; buf = new ByteList(internal.getByteList(), (int) pos, length); } } } else { int rest = (int) (internal.getByteList().length() - pos); if (length > rest) length = rest; // Yow...this is still ugly byte[] target = buf.bytes; if (target.length > length) { System.arraycopy(internal.getByteList().bytes, (int) pos, target, 0, length); buf.begin = 0; buf.realSize = length; } else { target = new byte[length]; System.arraycopy(internal.getByteList().bytes, (int) pos, target, 0, length); buf.begin = 0; buf.realSize = length; buf.bytes = target; } } if (buf == null) { if (!eof) buf = new ByteList(); length = 0; } else { length = buf.length(); pos += length; } if (oldLength < 0 || oldLength > length) eof = true; return originalString != null ? originalString : getRuntime().newString(buf); } @JRubyMethod(name = "readchar") public IRubyObject readchar() { IRubyObject c = getc(); if (c.isNil()) throw getRuntime().newEOFError(); return c; } @JRubyMethod(name = "readline", optional = 1, writes = FrameField.LASTLINE) public IRubyObject readline(ThreadContext context, IRubyObject[] args) { IRubyObject line = gets(context, args); if (line.isNil()) throw getRuntime().newEOFError(); return line; } @JRubyMethod(name = "readlines", optional = 1) public IRubyObject readlines(ThreadContext context, IRubyObject[] arg) { checkReadable(); List<IRubyObject> lns = new ArrayList<IRubyObject>(); while (!(isEOF())) { IRubyObject line = internalGets(context, arg); if (line.isNil()) { break; } lns.add(line); } return getRuntime().newArray(lns); } @JRubyMethod(name = "reopen", required = 0, optional = 2) public IRubyObject reopen(IRubyObject[] args) { if (args.length == 1 && !(args[0] instanceof RubyString)) { return initialize_copy(args[0]); } // reset the state doRewind(); closedRead = false; closedWrite = false; return initialize(args, Block.NULL_BLOCK); } @JRubyMethod(name = "rewind") public IRubyObject rewind() { doRewind(); return RubyFixnum.zero(getRuntime()); } private void doRewind() { this.pos = 0L; this.eof = false; this.lineno = 0; } @JRubyMethod(name = "seek", required = 1, optional = 1, frame = true) public IRubyObject seek(IRubyObject[] args) { // MRI 1.8.7 behavior: // checkOpen(); checkFinalized(); long amount = RubyNumeric.num2long(args[0]); int whence = Stream.SEEK_SET; long newPosition = pos; if (args.length > 1 && !args[0].isNil()) whence = RubyNumeric.fix2int(args[1]); if (whence == Stream.SEEK_CUR) { newPosition += amount; } else if (whence == Stream.SEEK_END) { newPosition = internal.getByteList().length() + amount; } else { newPosition = amount; } if (newPosition < 0) throw getRuntime().newErrnoEINVALError(); pos = newPosition; eof = false; return RubyFixnum.zero(getRuntime()); } @JRubyMethod(name = "string=", required = 1) public IRubyObject set_string(IRubyObject arg) { return reopen(new IRubyObject[] {arg.convertToString()}); } @JRubyMethod(name = "sync=", required = 1) public IRubyObject set_sync(IRubyObject args) { return args; } @JRubyMethod(name = "string") public IRubyObject string() { if (internal == null) { return getRuntime().getNil(); } else { return internal; } } @JRubyMethod(name = "sync") public IRubyObject sync() { return getRuntime().getTrue(); } @JRubyMethod(name = "sysread", optional = 2) public IRubyObject sysread(IRubyObject[] args) { IRubyObject obj = read(args); if (isEOF()) { if (obj.isNil() || ((RubyString) obj).getByteList().length() == 0) { throw getRuntime().newEOFError(); } } return obj; } @JRubyMethod(name = "truncate", required = 1) public IRubyObject truncate(IRubyObject arg) { checkWritable(); int len = RubyFixnum.fix2int(arg); if (len < 0) { throw getRuntime().newErrnoEINVALError("negative legnth"); } internal.modify(); internal.getByteList().length(len); return arg; } @JRubyMethod(name = "ungetc", required = 1) public IRubyObject ungetc(IRubyObject arg) { checkReadable(); int c = RubyNumeric.num2int(arg); if (pos == 0) return getRuntime().getNil(); internal.modify(); pos--; ByteList bytes = internal.getByteList(); if (bytes.length() <= pos) { bytes.length((int) pos + 1); } bytes.set((int) pos, c); return getRuntime().getNil(); } @JRubyMethod( name = {"write", "syswrite"}, required = 1) public IRubyObject write(ThreadContext context, IRubyObject arg) { return context.getRuntime().newFixnum(writeInternal(context, arg)); } private int writeInternal(ThreadContext context, IRubyObject arg) { checkWritable(); checkFrozen(); RubyString val = arg.asString(); internal.modify(); if (modes.isAppendable()) { internal.getByteList().append(val.getByteList()); pos = internal.getByteList().length(); } else { int left = internal.getByteList().length() - (int) pos; internal .getByteList() .replace((int) pos, Math.min(val.getByteList().length(), left), val.getByteList()); pos += val.getByteList().length(); } if (val.isTaint()) { internal.setTaint(true); } return val.getByteList().length(); } /* rb: check_modifiable */ @Override protected void checkFrozen() { checkInitialized(); if (internal.isFrozen()) throw getRuntime().newIOError("not modifiable string"); } /* rb: readable */ private void checkReadable() { checkInitialized(); if (closedRead || !modes.isReadable()) { throw getRuntime().newIOError("not opened for reading"); } } /* rb: writable */ private void checkWritable() { checkInitialized(); if (closedWrite || !modes.isWritable()) { throw getRuntime().newIOError("not opened for writing"); } // Tainting here if we ever want it. (secure 4) } private void checkInitialized() { if (modes == null) { throw getRuntime().newIOError("uninitialized stream"); } } private void checkFinalized() { if (internal == null) { throw getRuntime().newIOError("not opened"); } } private void checkOpen() { if (closedRead && closedWrite) { throw getRuntime().newIOError("closed stream"); } } private void setupModes() { closedWrite = false; closedRead = false; if (modes.isReadOnly()) closedWrite = true; if (!modes.isReadable()) closedRead = true; } }
/** Deprecated shim for what's now in org.jruby.ext.stringio.RubyStringIO */ @JRubyClass(name = "StringIO") public abstract class RubyStringIO extends RubyObject { protected RubyStringIO(Ruby runtime, RubyClass klass) { super(runtime, klass); } public static IRubyObject open( ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { return org.jruby.ext.stringio.RubyStringIO.open(context, recv, args, block); } public abstract IRubyObject initialize(IRubyObject[] args, Block unusedBlock); public abstract IRubyObject initialize_copy(IRubyObject other); public abstract IRubyObject append(ThreadContext context, IRubyObject arg); public abstract IRubyObject binmode(); public abstract IRubyObject close(); public abstract IRubyObject closed_p(); public abstract IRubyObject close_read(); public abstract IRubyObject closed_read_p(); public abstract IRubyObject close_write(); public abstract IRubyObject closed_write_p(); public abstract IRubyObject eachInternal(ThreadContext context, IRubyObject[] args, Block block); public abstract IRubyObject each(ThreadContext context, IRubyObject[] args, Block block); public abstract IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block block); public abstract IRubyObject lines(ThreadContext context, IRubyObject[] args, Block block); public abstract IRubyObject each_byte(ThreadContext context, Block block); public abstract IRubyObject each_byte19(ThreadContext context, Block block); public abstract IRubyObject bytes(ThreadContext context, Block block); public abstract IRubyObject each_charInternal(final ThreadContext context, final Block block); public abstract IRubyObject each_char(final ThreadContext context, final Block block); public abstract IRubyObject chars(final ThreadContext context, final Block block); public abstract IRubyObject eof(); public abstract IRubyObject fcntl(); public abstract IRubyObject fileno(); public abstract IRubyObject flush(); public abstract IRubyObject fsync(); public abstract IRubyObject getc(); public abstract IRubyObject getc19(ThreadContext context); public abstract IRubyObject gets(ThreadContext context, IRubyObject[] args); public abstract IRubyObject gets19(ThreadContext context, IRubyObject[] args); public abstract IRubyObject getsOnly(ThreadContext context, IRubyObject[] args); public abstract IRubyObject isatty(); public abstract IRubyObject length(); public abstract IRubyObject lineno(); public abstract IRubyObject set_lineno(IRubyObject arg); public abstract IRubyObject path(); public abstract IRubyObject pid(); public abstract IRubyObject pos(); public abstract IRubyObject set_pos(IRubyObject arg); public abstract IRubyObject print(ThreadContext context, IRubyObject[] args); public abstract IRubyObject print19(ThreadContext context, IRubyObject[] args); public abstract IRubyObject printf(ThreadContext context, IRubyObject[] args); public abstract IRubyObject putc(IRubyObject obj); public static final ByteList NEWLINE = ByteList.create("\n"); public abstract IRubyObject puts(ThreadContext context, IRubyObject[] args); public abstract IRubyObject read(IRubyObject[] args); public abstract IRubyObject read_nonblock(ThreadContext contet, IRubyObject[] args); public abstract IRubyObject readpartial(ThreadContext context, IRubyObject[] args); public abstract IRubyObject readchar(); public abstract IRubyObject readchar19(ThreadContext context); public abstract IRubyObject readline(ThreadContext context, IRubyObject[] args); public abstract IRubyObject readlines(ThreadContext context, IRubyObject[] arg); public abstract IRubyObject reopen(IRubyObject[] args); public abstract IRubyObject rewind(); public abstract IRubyObject seek(IRubyObject[] args); public abstract IRubyObject set_string(IRubyObject arg); public abstract IRubyObject set_sync(IRubyObject args); public abstract IRubyObject string(); public abstract IRubyObject sync(); public abstract IRubyObject sysread(IRubyObject[] args); public abstract IRubyObject truncate(IRubyObject arg); public abstract IRubyObject ungetc(IRubyObject arg); public abstract IRubyObject ungetc19(ThreadContext context, IRubyObject arg); public abstract IRubyObject write(ThreadContext context, IRubyObject arg); public abstract IRubyObject set_encoding(ThreadContext context, IRubyObject enc); public abstract IRubyObject external_encoding(ThreadContext context); public abstract IRubyObject internal_encoding(ThreadContext context); public abstract void checkFrozen(); }
/** * The Time class. * * @author chadfowler, jpetersen */ @JRubyClass(name = "Time", include = "Comparable") public class RubyTime extends RubyObject { public static final String UTC = "UTC"; private DateTime dt; private long usec; private static final DateTimeFormatter ONE_DAY_CTIME_FORMATTER = DateTimeFormat.forPattern("EEE MMM d HH:mm:ss yyyy").withLocale(Locale.ENGLISH); private static final DateTimeFormatter TWO_DAY_CTIME_FORMATTER = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss yyyy").withLocale(Locale.ENGLISH); private static final DateTimeFormatter TO_S_FORMATTER = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss Z yyyy").withLocale(Locale.ENGLISH); private static final DateTimeFormatter TO_S_UTC_FORMATTER = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss 'UTC' yyyy").withLocale(Locale.ENGLISH); private static final DateTimeFormatter TO_S_FORMATTER_19 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss Z").withLocale(Locale.ENGLISH); private static final DateTimeFormatter TO_S_UTC_FORMATTER_19 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss 'UTC'").withLocale(Locale.ENGLISH); // There are two different popular TZ formats: legacy (AST+3:00:00, GMT-3), and // newer one (US/Pacific, America/Los_Angeles). This pattern is to detect // the legacy TZ format in order to convert it to the newer format // understood by Java API. private static final Pattern TZ_PATTERN = Pattern.compile("(\\D+?)([\\+-]?)(\\d+)(:\\d+)?(:\\d+)?"); private static final Pattern TIME_OFFSET_PATTERN = Pattern.compile("([\\+-])(\\d\\d):\\d\\d"); private static final ByteList TZ_STRING = ByteList.create("TZ"); /* JRUBY-3560 * joda-time disallows use of three-letter time zone IDs. * Since MRI accepts these values, we need to translate them. */ private static final Map<String, String> LONG_TZNAME = new HashMap<String, String>() { { put("MET", "CET"); // JRUBY-2579 put("ROC", "Asia/Taipei"); // Republic of China put("WET", "Europe/Lisbon"); // Western European Time } }; /* Some TZ values need to be overriden for Time#zone */ private static final Map<String, String> SHORT_STD_TZNAME = new HashMap<String, String>() { { put("Etc/UCT", "UCT"); put("MET", "MET"); // needs to be overriden put("UCT", "UCT"); } }; private static final Map<String, String> SHORT_DL_TZNAME = new HashMap<String, String>() { { put("Etc/UCT", "UCT"); put("MET", "MEST"); // needs to be overriden put("UCT", "UCT"); } }; @Override public int getNativeTypeIndex() { return ClassIndex.TIME; } private static IRubyObject getEnvTimeZone(Ruby runtime) { RubyString tzVar = runtime.newString(TZ_STRING); RubyHash h = ((RubyHash) runtime.getObject().getConstant("ENV")); IRubyObject tz = h.op_aref(runtime.getCurrentContext(), tzVar); return tz; } public static DateTimeZone getLocalTimeZone(Ruby runtime) { IRubyObject tz = getEnvTimeZone(runtime); if (tz == null || !(tz instanceof RubyString)) { return DateTimeZone.getDefault(); } else { return getTimeZone(runtime, tz.toString()); } } public static DateTimeZone getTimeZone(Ruby runtime, String zone) { DateTimeZone cachedZone = runtime.getTimezoneCache().get(zone); if (cachedZone != null) return cachedZone; String originalZone = zone; TimeZone tz = TimeZone.getTimeZone(getEnvTimeZone(runtime).toString()); // Value of "TZ" property is of a bit different format, // which confuses the Java's TimeZone.getTimeZone(id) method, // and so, we need to convert it. Matcher tzMatcher = TZ_PATTERN.matcher(zone); if (tzMatcher.matches()) { String sign = tzMatcher.group(2); String hours = tzMatcher.group(3); String minutes = tzMatcher.group(4); // GMT+00:00 --> Etc/GMT, see "MRI behavior" // comment below. if (("00".equals(hours) || "0".equals(hours)) && (minutes == null || ":00".equals(minutes) || ":0".equals(minutes))) { zone = "Etc/GMT"; } else { // Invert the sign, since TZ format and Java format // use opposite signs, sigh... Also, Java API requires // the sign to be always present, be it "+" or "-". sign = ("-".equals(sign) ? "+" : "-"); // Always use "GMT" since that's required by Java API. zone = "GMT" + sign + hours; if (minutes != null) { zone += minutes; } } tz = TimeZone.getTimeZone(zone); } else { if (LONG_TZNAME.containsKey(zone)) tz.setID(LONG_TZNAME.get(zone.toUpperCase())); } // MRI behavior: With TZ equal to "GMT" or "UTC", Time.now // is *NOT* considered as a proper GMT/UTC time: // ENV['TZ']="GMT" // Time.now.gmt? ==> false // ENV['TZ']="UTC" // Time.now.utc? ==> false // Hence, we need to adjust for that. if ("GMT".equalsIgnoreCase(zone) || "UTC".equalsIgnoreCase(zone)) { zone = "Etc/" + zone; tz = TimeZone.getTimeZone(zone); } DateTimeZone dtz = DateTimeZone.forTimeZone(tz); runtime.getTimezoneCache().put(originalZone, dtz); return dtz; } public RubyTime(Ruby runtime, RubyClass rubyClass) { super(runtime, rubyClass); } public RubyTime(Ruby runtime, RubyClass rubyClass, DateTime dt) { super(runtime, rubyClass); this.dt = dt; } private static ObjectAllocator TIME_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { DateTimeZone dtz = getLocalTimeZone(runtime); DateTime dt = new DateTime(dtz); RubyTime rt = new RubyTime(runtime, klass, dt); rt.setUSec(0); return rt; } }; public static RubyClass createTimeClass(Ruby runtime) { RubyClass timeClass = runtime.defineClass("Time", runtime.getObject(), TIME_ALLOCATOR); timeClass.index = ClassIndex.TIME; timeClass.setReifiedClass(RubyTime.class); runtime.setTime(timeClass); timeClass.includeModule(runtime.getComparable()); timeClass.defineAnnotatedMethods(RubyTime.class); return timeClass; } public void setUSec(long usec) { this.usec = usec; } public long getUSec() { return usec; } public void updateCal(DateTime dt) { this.dt = dt; } protected long getTimeInMillis() { return dt.getMillis(); // For JDK 1.4 we can use "cal.getTimeInMillis()" } public static RubyTime newTime(Ruby runtime, long milliseconds) { return newTime(runtime, new DateTime(milliseconds)); } public static RubyTime newTime(Ruby runtime, DateTime dt) { return new RubyTime(runtime, runtime.getTime(), dt); } public static RubyTime newTime(Ruby runtime, DateTime dt, long usec) { RubyTime t = new RubyTime(runtime, runtime.getTime(), dt); t.setUSec(usec); return t; } @Override public Class<?> getJavaClass() { return Date.class; } @JRubyMethod(name = "initialize_copy", required = 1) @Override public IRubyObject initialize_copy(IRubyObject original) { if (!(original instanceof RubyTime)) { throw getRuntime().newTypeError("Expecting an instance of class Time"); } RubyTime originalTime = (RubyTime) original; // We can just use dt, since it is immutable dt = originalTime.dt; usec = originalTime.usec; return this; } @JRubyMethod(name = "succ") public RubyTime succ() { return newTime(getRuntime(), dt.plusSeconds(1)); } @JRubyMethod(name = {"gmtime", "utc"}) public RubyTime gmtime() { dt = dt.withZone(DateTimeZone.UTC); return this; } @JRubyMethod(name = "localtime") public RubyTime localtime() { dt = dt.withZone(getLocalTimeZone(getRuntime())); return this; } @JRubyMethod(name = {"gmt?", "utc?", "gmtime?"}) public RubyBoolean gmt() { return getRuntime().newBoolean(dt.getZone().getID().equals("UTC")); } @JRubyMethod(name = {"getgm", "getutc"}) public RubyTime getgm() { return newTime(getRuntime(), dt.withZone(DateTimeZone.UTC), getUSec()); } @JRubyMethod(name = "getlocal") public RubyTime getlocal() { return newTime(getRuntime(), dt.withZone(getLocalTimeZone(getRuntime())), getUSec()); } @JRubyMethod(name = "strftime", required = 1) public RubyString strftime(IRubyObject format) { final RubyDateFormat rubyDateFormat = new RubyDateFormat("-", Locale.US, getRuntime().is1_9()); rubyDateFormat.applyPattern(format.convertToString().getUnicodeValue()); rubyDateFormat.setDateTime(dt); String result = rubyDateFormat.format(null); return getRuntime().newString(result); } @JRubyMethod(name = "==", required = 1, compat = CompatVersion.RUBY1_9) public IRubyObject op_equal(ThreadContext context, IRubyObject other) { if (other.isNil()) { return RubyBoolean.newBoolean(getRuntime(), false); } else if (other instanceof RubyTime) { return getRuntime().newBoolean(cmp((RubyTime) other) == 0); } return RubyComparable.op_equal(context, this, other); } @JRubyMethod(name = ">=", required = 1) public IRubyObject op_ge(ThreadContext context, IRubyObject other) { if (other instanceof RubyTime) { return getRuntime().newBoolean(cmp((RubyTime) other) >= 0); } return RubyComparable.op_ge(context, this, other); } @JRubyMethod(name = ">", required = 1) public IRubyObject op_gt(ThreadContext context, IRubyObject other) { if (other instanceof RubyTime) { return getRuntime().newBoolean(cmp((RubyTime) other) > 0); } return RubyComparable.op_gt(context, this, other); } @JRubyMethod(name = "<=", required = 1) public IRubyObject op_le(ThreadContext context, IRubyObject other) { if (other instanceof RubyTime) { return getRuntime().newBoolean(cmp((RubyTime) other) <= 0); } return RubyComparable.op_le(context, this, other); } @JRubyMethod(name = "<", required = 1) public IRubyObject op_lt(ThreadContext context, IRubyObject other) { if (other instanceof RubyTime) { return getRuntime().newBoolean(cmp((RubyTime) other) < 0); } return RubyComparable.op_lt(context, this, other); } private int cmp(RubyTime other) { long millis = getTimeInMillis(); long millis_other = other.getTimeInMillis(); long usec_other = other.usec; if (millis > millis_other || (millis == millis_other && usec > usec_other)) { return 1; } else if (millis < millis_other || (millis == millis_other && usec < usec_other)) { return -1; } return 0; } @JRubyMethod(name = "+", required = 1, compat = CompatVersion.RUBY1_8) public IRubyObject op_plus(IRubyObject other) { if (other instanceof RubyTime) { throw getRuntime().newTypeError("time + time ?"); } long adjustment = Math.round(RubyNumeric.num2dbl(other) * 1000000); return opPlusCommon(adjustment); } @JRubyMethod(name = "+", required = 1, compat = CompatVersion.RUBY1_9) public IRubyObject op_plus19(ThreadContext context, IRubyObject other) { checkOpCoercion(context, other); if (other instanceof RubyTime) { throw getRuntime().newTypeError("time + time ?"); } other = other.callMethod(context, "to_r"); long adjustment = new Double(RubyNumeric.num2dbl(other) * 1000000).longValue(); return opPlusCommon(adjustment); } private IRubyObject opPlusCommon(long adjustment) { long micro = adjustment % 1000; adjustment = adjustment / 1000; long time = getTimeInMillis(); time += adjustment; if ((getUSec() + micro) >= 1000) { time++; micro = (getUSec() + micro) - 1000; } else { micro = getUSec() + micro; } RubyTime newTime = new RubyTime(getRuntime(), getMetaClass()); newTime.dt = new DateTime(time).withZone(dt.getZone()); newTime.setUSec(micro); return newTime; } private void checkOpCoercion(ThreadContext context, IRubyObject other) { if (other instanceof RubyString) { throw context.getRuntime().newTypeError("no implicit conversion to rational from string"); } else if (other.isNil()) { throw context.getRuntime().newTypeError("no implicit conversion to rational from nil"); } else if (!other.respondsTo("to_r")) { throw context .getRuntime() .newTypeError("can't convert " + other.getMetaClass().getBaseName() + " into Rational"); } } private IRubyObject opMinus(RubyTime other) { long time = getTimeInMillis() * 1000 + getUSec(); time -= other.getTimeInMillis() * 1000 + other.getUSec(); return RubyFloat.newFloat(getRuntime(), time / 1000000.0); // float number of seconds } @JRubyMethod(name = "-", required = 1, compat = CompatVersion.RUBY1_8) public IRubyObject op_minus(IRubyObject other) { if (other instanceof RubyTime) return opMinus((RubyTime) other); return opMinusCommon(other); } @JRubyMethod(name = "-", required = 1, compat = CompatVersion.RUBY1_9) public IRubyObject op_minus19(ThreadContext context, IRubyObject other) { checkOpCoercion(context, other); if (other instanceof RubyTime) return opMinus((RubyTime) other); return opMinusCommon(other.callMethod(context, "to_r")); } private IRubyObject opMinusCommon(IRubyObject other) { long time = getTimeInMillis(); long adjustment = Math.round(RubyNumeric.num2dbl(other) * 1000000); long micro = adjustment % 1000; adjustment = adjustment / 1000; time -= adjustment; if (getUSec() < micro) { time--; micro = 1000 - (micro - getUSec()); } else { micro = getUSec() - micro; } RubyTime newTime = new RubyTime(getRuntime(), getMetaClass()); newTime.dt = new DateTime(time).withZone(dt.getZone()); newTime.setUSec(micro); return newTime; } @JRubyMethod(name = "===", required = 1) @Override public IRubyObject op_eqq(ThreadContext context, IRubyObject other) { return (RubyNumeric.fix2int(invokedynamic(context, this, OP_CMP, other)) == 0) ? getRuntime().getTrue() : getRuntime().getFalse(); } @JRubyMethod(name = "<=>", required = 1) public IRubyObject op_cmp(ThreadContext context, IRubyObject other) { if (other instanceof RubyTime) { return context.getRuntime().newFixnum(cmp((RubyTime) other)); } return context.getRuntime().getNil(); } @JRubyMethod(name = "eql?", required = 1) @Override public IRubyObject eql_p(IRubyObject other) { if (other instanceof RubyTime) { RubyTime otherTime = (RubyTime) other; return (usec == otherTime.usec && getTimeInMillis() == otherTime.getTimeInMillis()) ? getRuntime().getTrue() : getRuntime().getFalse(); } return getRuntime().getFalse(); } @JRubyMethod(name = {"asctime", "ctime"}) public RubyString asctime() { DateTimeFormatter simpleDateFormat; if (dt.getDayOfMonth() < 10) { simpleDateFormat = ONE_DAY_CTIME_FORMATTER; } else { simpleDateFormat = TWO_DAY_CTIME_FORMATTER; } String result = simpleDateFormat.print(dt); return getRuntime().newString(result); } @JRubyMethod( name = {"to_s", "inspect"}, compat = CompatVersion.RUBY1_8) @Override public IRubyObject to_s() { return inspectCommon(TO_S_FORMATTER, TO_S_UTC_FORMATTER); } @JRubyMethod( name = {"to_s", "inspect"}, compat = CompatVersion.RUBY1_9) public IRubyObject to_s19() { return inspectCommon(TO_S_FORMATTER_19, TO_S_UTC_FORMATTER_19); } private IRubyObject inspectCommon(DateTimeFormatter formatter, DateTimeFormatter utcFormatter) { DateTimeFormatter simpleDateFormat; if (dt.getZone() == DateTimeZone.UTC) { simpleDateFormat = utcFormatter; } else { simpleDateFormat = formatter; } String result = simpleDateFormat.print(dt); return getRuntime().newString(result); } @JRubyMethod(name = "to_a") @Override public RubyArray to_a() { return getRuntime() .newArrayNoCopy( new IRubyObject[] { sec(), min(), hour(), mday(), month(), year(), wday(), yday(), isdst(), zone() }); } @JRubyMethod(name = "to_f") public RubyFloat to_f() { long time = getTimeInMillis(); time = time * 1000 + usec; return RubyFloat.newFloat(getRuntime(), time / 1000000.0); } @JRubyMethod(name = {"to_i", "tv_sec"}) public RubyInteger to_i() { return getRuntime().newFixnum(getTimeInMillis() / 1000); } @JRubyMethod( name = {"nsec", "tv_nsec"}, compat = RUBY1_9) public RubyInteger nsec() { return getRuntime().newFixnum(0); } @JRubyMethod(name = "to_r", backtrace = true, compat = CompatVersion.RUBY1_9) public IRubyObject to_r(ThreadContext context) { IRubyObject rational = to_f().to_r(context); if (rational instanceof RubyRational) { IRubyObject denominator = ((RubyRational) rational).denominator(context); if (RubyNumeric.num2long(denominator) == 1) { return ((RubyRational) rational).numerator(context); } } return rational; } @JRubyMethod(name = {"usec", "tv_usec"}) public RubyInteger usec() { return getRuntime().newFixnum(dt.getMillisOfSecond() * 1000 + getUSec()); } public void setMicroseconds(long mic) { long millis = getTimeInMillis() % 1000; long withoutMillis = getTimeInMillis() - millis; withoutMillis += (mic / 1000); dt = dt.withMillis(withoutMillis); usec = mic % 1000; } public long microseconds() { return getTimeInMillis() % 1000 * 1000 + usec; } @JRubyMethod(name = "sec") public RubyInteger sec() { return getRuntime().newFixnum(dt.getSecondOfMinute()); } @JRubyMethod(name = "min") public RubyInteger min() { return getRuntime().newFixnum(dt.getMinuteOfHour()); } @JRubyMethod(name = "hour") public RubyInteger hour() { return getRuntime().newFixnum(dt.getHourOfDay()); } @JRubyMethod(name = {"mday", "day"}) public RubyInteger mday() { return getRuntime().newFixnum(dt.getDayOfMonth()); } @JRubyMethod(name = {"month", "mon"}) public RubyInteger month() { return getRuntime().newFixnum(dt.getMonthOfYear()); } @JRubyMethod(name = "year") public RubyInteger year() { return getRuntime().newFixnum(dt.getYear()); } @JRubyMethod(name = "wday") public RubyInteger wday() { return getRuntime().newFixnum((dt.getDayOfWeek() % 7)); } @JRubyMethod(name = "yday") public RubyInteger yday() { return getRuntime().newFixnum(dt.getDayOfYear()); } @JRubyMethod(name = "subsec", compat = CompatVersion.RUBY1_9) public RubyRational subsec() { // TODO: nanosecond resolution (JSR310?) return getRuntime().newRational(dt.getMillisOfSecond(), 1000); } @JRubyMethod(name = {"gmt_offset", "gmtoff", "utc_offset"}) public RubyInteger gmt_offset() { int offset = dt.getZone().getOffset(dt.getMillis()); return getRuntime().newFixnum((int) (offset / 1000)); } @JRubyMethod(name = {"isdst", "dst?"}) public RubyBoolean isdst() { return getRuntime().newBoolean(!dt.getZone().isStandardOffset(dt.getMillis())); } @JRubyMethod(name = "zone") public RubyString zone() { Ruby runtime = getRuntime(); String envTZ = getEnvTimeZone(runtime).toString(); // see declaration of SHORT_TZNAME if (SHORT_STD_TZNAME.containsKey(envTZ) && !dt.getZone().toTimeZone().inDaylightTime(dt.toDate())) { return runtime.newString(SHORT_STD_TZNAME.get(envTZ)); } if (SHORT_DL_TZNAME.containsKey(envTZ) && dt.getZone().toTimeZone().inDaylightTime(dt.toDate())) { return runtime.newString(SHORT_DL_TZNAME.get(envTZ)); } String zone = dt.getZone().getShortName(dt.getMillis()); Matcher offsetMatcher = TIME_OFFSET_PATTERN.matcher(zone); if (offsetMatcher.matches()) { boolean minus_p = offsetMatcher.group(1).toString().equals("-"); int hourOffset = Integer.valueOf(offsetMatcher.group(2)); if (zone.equals("+00:00")) { zone = "GMT"; } else { // try non-localized time zone name zone = dt.getZone().getNameKey(dt.getMillis()); if (zone == null) { char sign = minus_p ? '+' : '-'; zone = "GMT" + sign + hourOffset; } } } return runtime.newString(zone); } public void setDateTime(DateTime dt) { this.dt = dt; } public DateTime getDateTime() { return this.dt; } public Date getJavaDate() { return this.dt.toDate(); } @JRubyMethod(name = "hash") @Override public RubyFixnum hash() { // modified to match how hash is calculated in 1.8.2 return getRuntime().newFixnum((int) (((dt.getMillis() / 1000) ^ microseconds()) << 1) >> 1); } @JRubyMethod(name = "_dump", optional = 1) public RubyString dump(IRubyObject[] args, Block unusedBlock) { RubyString str = (RubyString) mdump(); str.syncVariables(this); return str; } public RubyObject mdump() { RubyTime obj = this; DateTime dateTime = obj.dt.toDateTime(DateTimeZone.UTC); byte dumpValue[] = new byte[8]; int pe = 0x1 << 31 | ((obj.gmt().isTrue()) ? 0x1 : 0x0) << 30 | (dateTime.getYear() - 1900) << 14 | (dateTime.getMonthOfYear() - 1) << 10 | dateTime.getDayOfMonth() << 5 | dateTime.getHourOfDay(); int se = dateTime.getMinuteOfHour() << 26 | dateTime.getSecondOfMinute() << 20 | (dateTime.getMillisOfSecond() * 1000 + (int) usec); // dump usec, not msec for (int i = 0; i < 4; i++) { dumpValue[i] = (byte) (pe & 0xFF); pe >>>= 8; } for (int i = 4; i < 8; i++) { dumpValue[i] = (byte) (se & 0xFF); se >>>= 8; } return RubyString.newString(obj.getRuntime(), new ByteList(dumpValue)); } @JRubyMethod(visibility = PRIVATE) public IRubyObject initialize(Block block) { return this; } /* Time class methods */ public static IRubyObject s_new(IRubyObject recv, IRubyObject[] args, Block block) { Ruby runtime = recv.getRuntime(); RubyTime time = new RubyTime(runtime, (RubyClass) recv, new DateTime(getLocalTimeZone(runtime))); time.callInit(args, block); return time; } /** @deprecated Use {@link #newInstance(ThreadContext, IRubyObject)} */ @Deprecated public static IRubyObject newInstance( ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { return newInstance(context, recv); } @JRubyMethod(name = "times", meta = true, compat = CompatVersion.RUBY1_8) public static IRubyObject times(ThreadContext context, IRubyObject recv) { context.getRuntime().getWarnings().warn("obsolete method Time::times; use Process::times"); return RubyProcess.times(context, recv, Block.NULL_BLOCK); } @JRubyMethod(name = "now", backtrace = true, meta = true) public static IRubyObject newInstance(ThreadContext context, IRubyObject recv) { IRubyObject obj = ((RubyClass) recv).allocate(); obj.getMetaClass().getBaseCallSites()[RubyClass.CS_IDX_INITIALIZE].call(context, recv, obj); return obj; } @JRubyMethod(name = "at", meta = true) public static IRubyObject at(ThreadContext context, IRubyObject recv, IRubyObject arg) { Ruby runtime = context.getRuntime(); final RubyTime time; if (arg instanceof RubyTime) { RubyTime other = (RubyTime) arg; time = new RubyTime(runtime, (RubyClass) recv, other.dt); time.setUSec(other.getUSec()); } else { time = new RubyTime(runtime, (RubyClass) recv, new DateTime(0L, getLocalTimeZone(runtime))); long seconds = RubyNumeric.num2long(arg); long millisecs = 0; long microsecs = 0; // In the case of two arguments, MRI will discard the portion of // the first argument after a decimal point (i.e., "floor"). // However in the case of a single argument, any portion after // the decimal point is honored. if (arg instanceof RubyFloat || arg instanceof RubyRational) { double dbl = RubyNumeric.num2dbl(arg); long micro = Math.round((dbl - seconds) * 1000000); if (dbl < 0 && micro != 0) { micro += 1000000; } millisecs = micro / 1000; microsecs = micro % 1000; } time.setUSec(microsecs); time.dt = time.dt.withMillis(seconds * 1000 + millisecs); } time.getMetaClass().getBaseCallSites()[RubyClass.CS_IDX_INITIALIZE].call(context, recv, time); return time; } @JRubyMethod(name = "at", meta = true) public static IRubyObject at( ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) { Ruby runtime = context.getRuntime(); RubyTime time = new RubyTime(runtime, (RubyClass) recv, new DateTime(0L, getLocalTimeZone(runtime))); long seconds = RubyNumeric.num2long(arg1); long millisecs = 0; long microsecs = 0; long tmp = RubyNumeric.num2long(arg2); millisecs = tmp / 1000; microsecs = tmp % 1000; time.setUSec(microsecs); time.dt = time.dt.withMillis(seconds * 1000 + millisecs); time.getMetaClass().getBaseCallSites()[RubyClass.CS_IDX_INITIALIZE].call(context, recv, time); return time; } @JRubyMethod( name = {"local", "mktime"}, required = 1, optional = 9, meta = true) public static RubyTime new_local(IRubyObject recv, IRubyObject[] args) { return createTime(recv, args, false); } @JRubyMethod(name = "new", optional = 10, meta = true, compat = RUBY1_9) public static IRubyObject new19(ThreadContext context, IRubyObject recv, IRubyObject[] args) { if (args.length == 0) { return newInstance(context, recv); } return createTime(recv, args, false); } @JRubyMethod( name = {"utc", "gm"}, required = 1, optional = 9, meta = true) public static RubyTime new_utc(IRubyObject recv, IRubyObject[] args) { return createTime(recv, args, true); } @JRubyMethod(name = "_load", meta = true) public static RubyTime load(IRubyObject recv, IRubyObject from, Block block) { return s_mload(recv, (RubyTime) (((RubyClass) recv).allocate()), from); } @Override public Object toJava(Class target) { if (target.equals(Date.class)) { return getJavaDate(); } else if (target.equals(Calendar.class)) { Calendar cal = GregorianCalendar.getInstance(); cal.setTime(getJavaDate()); return cal; } else if (target.equals(DateTime.class)) { return this.dt; } else if (target.equals(java.sql.Date.class)) { return new java.sql.Date(dt.getMillis()); } else if (target.equals(java.sql.Time.class)) { return new java.sql.Time(dt.getMillis()); } else if (target.equals(java.sql.Timestamp.class)) { return new java.sql.Timestamp(dt.getMillis()); } else if (target.isAssignableFrom(Date.class)) { return getJavaDate(); } else { return super.toJava(target); } } protected static RubyTime s_mload(IRubyObject recv, RubyTime time, IRubyObject from) { Ruby runtime = recv.getRuntime(); DateTime dt = new DateTime(DateTimeZone.UTC); byte[] fromAsBytes = null; fromAsBytes = from.convertToString().getBytes(); if (fromAsBytes.length != 8) { throw runtime.newTypeError("marshaled time format differ"); } int p = 0; int s = 0; for (int i = 0; i < 4; i++) { p |= ((int) fromAsBytes[i] & 0xFF) << (8 * i); } for (int i = 4; i < 8; i++) { s |= ((int) fromAsBytes[i] & 0xFF) << (8 * (i - 4)); } boolean utc = false; if ((p & (1 << 31)) == 0) { dt = dt.withMillis(p * 1000L); time.setUSec((s & 0xFFFFF) % 1000); } else { p &= ~(1 << 31); utc = ((p >>> 30 & 0x1) == 0x1); dt = dt.withYear(((p >>> 14) & 0xFFFF) + 1900); dt = dt.withMonthOfYear(((p >>> 10) & 0xF) + 1); dt = dt.withDayOfMonth(((p >>> 5) & 0x1F)); dt = dt.withHourOfDay((p & 0x1F)); dt = dt.withMinuteOfHour(((s >>> 26) & 0x3F)); dt = dt.withSecondOfMinute(((s >>> 20) & 0x3F)); // marsaling dumps usec, not msec dt = dt.withMillisOfSecond((s & 0xFFFFF) / 1000); time.setUSec((s & 0xFFFFF) % 1000); } time.setDateTime(dt); if (!utc) time.localtime(); from.getInstanceVariables().copyInstanceVariablesInto(time); return time; } private static final String[] MONTHS = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; private static final Map<String, Integer> MONTHS_MAP = new HashMap<String, Integer>(); static { for (int i = 0; i < MONTHS.length; i++) { MONTHS_MAP.put(MONTHS[i], i + 1); } } private static final int[] time_min = {1, 0, 0, 0, Integer.MIN_VALUE}; private static final int[] time_max = {31, 23, 59, 60, Integer.MAX_VALUE}; private static final int ARG_SIZE = 7; private static RubyTime createTime(IRubyObject recv, IRubyObject[] args, boolean gmt) { Ruby runtime = recv.getRuntime(); int len = ARG_SIZE; Boolean isDst = null; DateTimeZone dtz; if (gmt) { dtz = DateTimeZone.UTC; } else if (args.length == 10 && args[9] instanceof RubyString) { dtz = getTimeZone(runtime, ((RubyString) args[9]).toString()); } else { dtz = getLocalTimeZone(runtime); } if (args.length == 10) { if (args[8] instanceof RubyBoolean) { isDst = ((RubyBoolean) args[8]).isTrue(); } args = new IRubyObject[] { args[5], args[4], args[3], args[2], args[1], args[0], runtime.getNil() }; } else { // MRI accepts additional wday argument which appears to be ignored. len = args.length; if (len < ARG_SIZE) { IRubyObject[] newArgs = new IRubyObject[ARG_SIZE]; System.arraycopy(args, 0, newArgs, 0, args.length); for (int i = len; i < ARG_SIZE; i++) { newArgs[i] = runtime.getNil(); } args = newArgs; len = ARG_SIZE; } } if (args[0] instanceof RubyString) { args[0] = RubyNumeric.str2inum(runtime, (RubyString) args[0], 10, false); } int year = (int) RubyNumeric.num2long(args[0]); int month = 1; if (len > 1) { if (!args[1].isNil()) { IRubyObject tmp = args[1].checkStringType(); if (!tmp.isNil()) { String monthString = tmp.toString().toLowerCase(); Integer monthInt = MONTHS_MAP.get(monthString); if (monthInt != null) { month = monthInt; } else { try { month = Integer.parseInt(monthString); } catch (NumberFormatException nfExcptn) { throw runtime.newArgumentError("Argument out of range."); } } } else { month = (int) RubyNumeric.num2long(args[1]); } } if (1 > month || month > 12) { throw runtime.newArgumentError("Argument out of range: for month: " + month); } } int[] int_args = {1, 0, 0, 0, 0, 0}; for (int i = 0; int_args.length >= i + 2; i++) { if (!args[i + 2].isNil()) { if (!(args[i + 2] instanceof RubyNumeric)) { args[i + 2] = args[i + 2].callMethod(runtime.getCurrentContext(), "to_i"); } long value = RubyNumeric.num2long(args[i + 2]); if (time_min[i] > value || value > time_max[i]) { throw runtime.newArgumentError("argument out of range."); } int_args[i] = (int) value; } } if (!runtime.is1_9()) { if (0 <= year && year < 39) { year += 2000; } else if (69 <= year && year < 139) { year += 1900; } } DateTime dt; // set up with min values and then add to allow rolling over try { dt = new DateTime(year, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC); dt = dt.plusMonths(month - 1) .plusDays(int_args[0] - 1) .plusHours(int_args[1]) .plusMinutes(int_args[2]) .plusSeconds(int_args[3]); if (runtime.is1_9() && !args[5].isNil()) { double millis = RubyFloat.num2dbl(args[5]); int int_millis = (int) (millis * 1000) % 1000; dt = dt.plusMillis(int_millis); } dt = dt.withZoneRetainFields(dtz); // we might need to perform a DST correction if (isDst != null) { // the instant at which we will ask dtz what the difference between DST and // standard time is long offsetCalculationInstant = dt.getMillis(); // if we might be moving this time from !DST -> DST, the offset is assumed // to be the same as it was just before we last moved from DST -> !DST if (dtz.isStandardOffset(dt.getMillis())) { offsetCalculationInstant = dtz.previousTransition(offsetCalculationInstant); } int offset = dtz.getStandardOffset(offsetCalculationInstant) - dtz.getOffset(offsetCalculationInstant); if (!isDst && !dtz.isStandardOffset(dt.getMillis())) { dt = dt.minusMillis(offset); } if (isDst && dtz.isStandardOffset(dt.getMillis())) { dt = dt.plusMillis(offset); } } } catch (org.joda.time.IllegalFieldValueException e) { throw runtime.newArgumentError("time out of range"); } RubyTime time = new RubyTime(runtime, (RubyClass) recv, dt); // Ignores usec if 8 args (for compatibility with parsedate) or if not supplied. if (args.length != 8 && !args[6].isNil()) { int usec = int_args[4] % 1000; int msec = int_args[4] / 1000; if (int_args[4] < 0) { msec -= 1; usec += 1000; } time.dt = dt.withMillis(dt.getMillis() + msec); time.setUSec(usec); } time.callInit(IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); return time; } }
@JRubyClass(name = "StringIO") @SuppressWarnings("deprecation") public class StringIO extends RubyObject implements EncodingCapable { static class StringIOData { /** * ATTN: the value of internal might be reset to null (during StringIO.open with block), so * watch out for that. */ RubyString string; int pos; int lineno; int flags; } StringIOData ptr; private static final int STRIO_READABLE = USER4_F; private static final int STRIO_WRITABLE = USER5_F; private static final int STRIO_READWRITE = (STRIO_READABLE | STRIO_WRITABLE); private static ObjectAllocator STRINGIO_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new StringIO(runtime, klass); } }; public static RubyClass createStringIOClass(final Ruby runtime) { RubyClass stringIOClass = runtime.defineClass("StringIO", runtime.getClass("Data"), STRINGIO_ALLOCATOR); stringIOClass.defineAnnotatedMethods(StringIO.class); stringIOClass.includeModule(runtime.getEnumerable()); if (runtime.getObject().isConstantDefined("Java")) { stringIOClass.defineAnnotatedMethods(IOJavaAddons.AnyIO.class); } return stringIOClass; } public Encoding getEncoding() { return ptr.string.getEncoding(); } public void setEncoding(Encoding e) { ptr.string.setEncoding(e); } @JRubyMethod(meta = true, rest = true) public static IRubyObject open( ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { StringIO strio = (StringIO) ((RubyClass) recv).newInstance(context, args, Block.NULL_BLOCK); IRubyObject val = strio; if (block.isGiven()) { try { val = block.yield(context, strio); } finally { strio.ptr.string = null; strio.flags &= ~STRIO_READWRITE; } } return val; } protected StringIO(Ruby runtime, RubyClass klass) { super(runtime, klass); } @JRubyMethod(optional = 2, visibility = PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { if (ptr == null) { ptr = new StringIOData(); } // does not dispatch quite right and is not really necessary for us // Helpers.invokeSuper(context, this, metaClass, "initialize", IRubyObject.NULL_ARRAY, // Block.NULL_BLOCK); strioInit(context, args); return this; } private void strioInit(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; RubyString string; IRubyObject mode; boolean trunc = false; switch (args.length) { case 2: mode = args[1]; if (mode instanceof RubyFixnum) { int flags = RubyFixnum.fix2int(mode); ptr.flags = ModeFlags.getOpenFileFlagsFor(flags); trunc = (flags & ModeFlags.TRUNC) != 0; } else { String m = args[1].convertToString().toString(); ptr.flags = OpenFile.ioModestrFmode(runtime, m); trunc = m.charAt(0) == 'w'; } string = args[0].convertToString(); if ((ptr.flags & OpenFile.WRITABLE) != 0 && string.isFrozen()) { throw runtime.newErrnoEACCESError("Permission denied"); } if (trunc) { string.resize(0); } break; case 1: string = args[0].convertToString(); ptr.flags = string.isFrozen() ? OpenFile.READABLE : OpenFile.READWRITE; break; case 0: string = RubyString.newEmptyString(runtime, runtime.getDefaultExternalEncoding()); ptr.flags = OpenFile.READWRITE; break; default: throw runtime.newArgumentError(args.length, 2); } ptr.string = string; ptr.pos = 0; ptr.lineno = 0; // funky way of shifting readwrite flags into object flags flags |= (ptr.flags & OpenFile.READWRITE) * (STRIO_READABLE / OpenFile.READABLE); } @JRubyMethod(visibility = PRIVATE) public IRubyObject initialize_copy(ThreadContext context, IRubyObject other) { StringIO otherIO = (StringIO) TypeConverter.convertToType(other, context.runtime.getClass("StringIO"), "to_strio"); if (this == otherIO) return this; ptr = otherIO.ptr; infectBy(otherIO); flags &= ~STRIO_READWRITE; flags |= otherIO.flags & STRIO_READWRITE; return this; } @JRubyMethod(name = {"binmode", "flush"}) public IRubyObject strio_self() { return this; } @JRubyMethod( name = {"fcntl"}, rest = true) public IRubyObject strio_unimpl(ThreadContext context, IRubyObject[] args) { throw context.runtime.newNotImplementedError(""); } @JRubyMethod(name = {"fsync"}) public IRubyObject strioZero(ThreadContext context) { return RubyFixnum.zero(context.runtime); } @JRubyMethod(name = {"sync="}) public IRubyObject strioFirst(IRubyObject arg) { checkInitialized(); return arg; } @JRubyMethod(name = {"isatty", "tty?"}) public IRubyObject strioFalse(ThreadContext context) { return context.runtime.getFalse(); } @JRubyMethod(name = {"pid", "fileno"}) public IRubyObject strioNil(ThreadContext context) { return context.nil; } @JRubyMethod(name = "<<", required = 1) public IRubyObject append(ThreadContext context, IRubyObject arg) { // Claims conversion is done via 'to_s' in docs. callMethod(context, "write", arg); return this; } @JRubyMethod public IRubyObject close(ThreadContext context) { checkInitialized(); checkOpen(); // NOTE: This is 2.0 behavior to allow dup'ed StringIO to remain open when original is closed flags &= ~STRIO_READWRITE; return context.nil; } @JRubyMethod(name = "closed?") public IRubyObject closed_p() { checkInitialized(); return getRuntime().newBoolean(closed()); } @JRubyMethod public IRubyObject close_read(ThreadContext context) { checkReadable(); flags &= ~STRIO_READABLE; return context.nil; } @JRubyMethod(name = "closed_read?") public IRubyObject closed_read_p() { checkInitialized(); return getRuntime().newBoolean(!readable()); } @JRubyMethod public IRubyObject close_write(ThreadContext context) { checkWritable(); flags &= ~STRIO_WRITABLE; return context.nil; } @JRubyMethod(name = "closed_write?") public IRubyObject closed_write_p() { checkInitialized(); return getRuntime().newBoolean(!writable()); } @JRubyMethod(name = "each", optional = 2, writes = FrameField.LASTLINE) public IRubyObject each(ThreadContext context, IRubyObject[] args, Block block) { if (!block.isGiven()) return enumeratorize(context.runtime, this, "each", args); IRubyObject line; if (args.length > 0 && !args[args.length - 1].isNil() && args[args.length - 1].checkStringType19().isNil() && RubyNumeric.num2long(args[args.length - 1]) == 0) { throw context.runtime.newArgumentError("invalid limit: 0 for each_line"); } checkReadable(); while (!(line = getline(context, args)).isNil()) { block.yieldSpecific(context, line); } return this; } @JRubyMethod(name = "each_line", optional = 2, writes = FrameField.LASTLINE) public IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block block) { if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", args); return each(context, args, block); } @JRubyMethod(name = "lines", optional = 2) public IRubyObject lines(ThreadContext context, IRubyObject[] args, Block block) { context.runtime.getWarnings().warn("StringIO#lines is deprecated; use #each_line instead"); return block.isGiven() ? each(context, args, block) : enumeratorize(context.runtime, this, "each_line", args); } @JRubyMethod(name = {"each_byte", "bytes"}) public IRubyObject each_byte(ThreadContext context, Block block) { if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_byte"); checkReadable(); Ruby runtime = context.runtime; ByteList bytes = ptr.string.getByteList(); // Check the length every iteration, since // the block can modify this string. while (ptr.pos < bytes.length()) { block.yield(context, runtime.newFixnum(bytes.get((int) ptr.pos++) & 0xFF)); } return this; } @JRubyMethod public IRubyObject each_char(final ThreadContext context, final Block block) { if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_char"); IRubyObject c; while (!(c = getc(context)).isNil()) { block.yieldSpecific(context, c); } return this; } @JRubyMethod public IRubyObject chars(final ThreadContext context, final Block block) { context.runtime.getWarnings().warn("StringIO#chars is deprecated; use #each_char instead"); return each_char(context, block); } @JRubyMethod(name = {"eof", "eof?"}) public IRubyObject eof(ThreadContext context) { checkReadable(); Ruby runtime = context.runtime; if (ptr.pos < ptr.string.size()) return runtime.getFalse(); return runtime.getTrue(); } private boolean isEndOfString() { return ptr.pos >= ptr.string.size(); } @JRubyMethod(name = "getc") public IRubyObject getc(ThreadContext context) { checkReadable(); if (isEndOfString()) return context.runtime.getNil(); int start = ptr.pos; int total = 1 + StringSupport.bytesToFixBrokenTrailingCharacter(ptr.string.getByteList(), start + 1); ptr.pos += total; return context.runtime.newString(ptr.string.getByteList().makeShared(start, total)); } @JRubyMethod(name = "getbyte") public IRubyObject getbyte(ThreadContext context) { checkReadable(); if (isEndOfString()) return context.runtime.getNil(); int c = ptr.string.getByteList().get(ptr.pos++) & 0xFF; return context.runtime.newFixnum(c); } private RubyString strioSubstr(Ruby runtime, int pos, int len) { RubyString str = ptr.string; ByteList strByteList = str.getByteList(); byte[] strBytes = strByteList.getUnsafeBytes(); Encoding enc = str.getEncoding(); int rlen = str.size() - pos; if (len > rlen) len = rlen; if (len < 0) len = 0; if (len == 0) return RubyString.newEmptyString(runtime); return RubyString.newStringShared(runtime, strBytes, strByteList.getBegin() + pos, len, enc); } private static int memchr(byte[] ptr, int start, int find, int len) { for (int i = start; i < start + len; i++) { if (ptr[i] == find) return i; } return -1; } private static final int CHAR_BIT = 8; private static void bm_init_skip(int[] skip, byte[] pat, int patPtr, int m) { int c; for (c = 0; c < (1 << CHAR_BIT); c++) { skip[c] = m; } while ((--m) > 0) { skip[pat[patPtr++]] = m; } } // Note that this is substantially more complex in 2.0 (Onigmo) private static int bm_search( byte[] little, int lstart, int llen, byte[] big, int bstart, int blen, int[] skip) { int i, j, k; i = llen - 1; while (i < blen) { k = i; j = llen - 1; while (j >= 0 && big[k + bstart] == little[j + lstart]) { k--; j--; } if (j < 0) return k + 1; i += skip[big[i + bstart] & 0xFF]; } return -1; } // if (sepArg != null) { // if (sepArg.isNil()) { // int bytesAvailable = data.internal.getByteList().getRealSize() - (int)data.pos; // int bytesToUse = (limit < 0 || limit >= bytesAvailable ? bytesAvailable : // limit); // // // add additional bytes to fix trailing broken character // bytesToUse += // StringSupport.bytesToFixBrokenTrailingCharacter(data.internal.getByteList(), bytesToUse); // // ByteList buf = data.internal.getByteList().makeShared( // (int)data.pos, bytesToUse); // data.pos += buf.getRealSize(); // return makeString(runtime, buf); // } // // sep = sepArg.convertToString().getByteList(); // if (sep.getRealSize() == 0) { // isParagraph = true; // sep = Stream.PARAGRAPH_SEPARATOR; // } // } // // if (isEndOfString() || data.eof) return context.nil; // // ByteList ss = data.internal.getByteList(); // // if (isParagraph) { // swallowLF(ss); // if (data.pos == ss.getRealSize()) { // return runtime.getNil(); // } // } // // int sepIndex = ss.indexOf(sep, (int)data.pos); // // ByteList add; // if (-1 == sepIndex) { // sepIndex = data.internal.getByteList().getRealSize(); // add = ByteList.EMPTY_BYTELIST; // } else { // add = sep; // } // // int bytes = sepIndex - (int)data.pos; // int bytesToUse = (limit < 0 || limit >= bytes ? bytes : limit); // // int bytesWithSep = sepIndex - (int)data.pos + add.getRealSize(); // int bytesToUseWithSep = (limit < 0 || limit >= bytesWithSep ? bytesWithSep : limit); // // ByteList line = new ByteList(bytesToUseWithSep); // if (is19) line.setEncoding(data.internal.getByteList().getEncoding()); // line.append(data.internal.getByteList(), (int)data.pos, bytesToUse); // data.pos += bytesToUse; // // if (is19) { // // add additional bytes to fix trailing broken character // int extraBytes = StringSupport.bytesToFixBrokenTrailingCharacter(line, // line.length()); // if (extraBytes != 0) { // line.append(data.internal.getByteList(), (int)data.pos, extraBytes); // data.pos += extraBytes; // } // } // // int sepBytesToUse = bytesToUseWithSep - bytesToUse; // line.append(add, 0, sepBytesToUse); // data.pos += sepBytesToUse; // // if (sepBytesToUse >= add.getRealSize()) { // data.lineno++; // } // // return makeString(runtime, line); // } // // private void swallowLF(ByteList list) { // while (ptr.pos < list.getRealSize()) { // if (list.get((int)ptr.pos) == '\n') { // ptr.pos++; // } else { // break; // } // } // } @JRubyMethod(name = "gets", optional = 2, writes = FrameField.LASTLINE) public IRubyObject gets(ThreadContext context, IRubyObject[] args) { checkReadable(); IRubyObject str = getline(context, args); context.setLastLine(str); return str; } // strio_getline private IRubyObject getline(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; IRubyObject str = context.nil; ; int n, limit = -1; switch (args.length) { case 0: str = runtime.getGlobalVariables().get("$/"); break; case 1: { str = args[0]; if (!str.isNil() && !(str instanceof RubyString)) { IRubyObject tmp = str.checkStringType19(); if (tmp.isNil()) { limit = RubyNumeric.num2int(str); if (limit == 0) return runtime.newString(); str = runtime.getGlobalVariables().get("$/"); } else { str = tmp; } } break; } case 2: if (!args[0].isNil()) str = args[0].convertToString(); // 2.0 ignores double nil, 1.9 raises if (runtime.is2_0()) { if (!args[1].isNil()) { limit = RubyNumeric.num2int(args[1]); } } else { limit = RubyNumeric.num2int(args[1]); } break; } if (isEndOfString()) { return context.nil; } ByteList dataByteList = ptr.string.getByteList(); byte[] dataBytes = dataByteList.getUnsafeBytes(); int begin = dataByteList.getBegin(); int s = begin + ptr.pos; int e = begin + dataByteList.getRealSize(); int p; if (limit > 0 && s + limit < e) { e = dataByteList.getEncoding().rightAdjustCharHead(dataBytes, s, s + limit, e); } if (str.isNil()) { str = strioSubstr(runtime, ptr.pos, e - s); } else if ((n = ((RubyString) str).size()) == 0) { // this is not an exact port; the original confused me p = s; // remove leading \n while (dataBytes[p] == '\n') { if (++p == e) { return context.nil; } } s = p; // find next \n or end; if followed by \n, include it too p = memchr(dataBytes, p, '\n', e - p); if (p != -1) { if (++p < e && dataBytes[p] == '\n') { e = p + 1; } else { e = p; } } str = strioSubstr(runtime, s - begin, e - s); } else if (n == 1) { RubyString strStr = (RubyString) str; ByteList strByteList = strStr.getByteList(); if ((p = memchr(dataBytes, s, strByteList.get(0), e - s)) != -1) { e = p + 1; } str = strioSubstr(runtime, ptr.pos, e - s); } else { if (n < e - s) { RubyString strStr = (RubyString) str; ByteList strByteList = strStr.getByteList(); byte[] strBytes = strByteList.getUnsafeBytes(); int[] skip = new int[1 << CHAR_BIT]; int pos; p = strByteList.getBegin(); bm_init_skip(skip, strBytes, p, n); if ((pos = bm_search(strBytes, p, n, dataBytes, s, e - s, skip)) >= 0) { e = s + pos + n; } } str = strioSubstr(runtime, ptr.pos, e - s); } ptr.pos = e - begin; ptr.lineno++; return str; } @JRubyMethod(name = {"length", "size"}) public IRubyObject length() { checkInitialized(); checkFinalized(); return getRuntime().newFixnum(ptr.string.size()); } @JRubyMethod(name = "lineno") public IRubyObject lineno(ThreadContext context) { return context.runtime.newFixnum(ptr.lineno); } @JRubyMethod(name = "lineno=", required = 1) public IRubyObject set_lineno(ThreadContext context, IRubyObject arg) { ptr.lineno = RubyNumeric.fix2int(arg); return context.nil; } @JRubyMethod(name = {"pos", "tell"}) public IRubyObject pos(ThreadContext context) { checkInitialized(); return context.runtime.newFixnum(ptr.pos); } @JRubyMethod(name = "pos=", required = 1) public IRubyObject set_pos(IRubyObject arg) { checkInitialized(); int p = RubyNumeric.fix2int(arg); if (p < 0) throw getRuntime().newErrnoEINVALError(arg.toString()); ptr.pos = p; return arg; } @JRubyMethod(name = "print", rest = true) public IRubyObject print(ThreadContext context, IRubyObject[] args) { return RubyIO.print19(context, this, args); } @JRubyMethod(name = "printf", required = 1, rest = true) public IRubyObject printf(ThreadContext context, IRubyObject[] args) { callMethod(context, "write", RubyKernel.sprintf(context, this, args)); return getRuntime().getNil(); } private void strioExtend(int pos, int len) { int olen; checkModifiable(); olen = ptr.string.size(); if (pos + len > olen) { ptr.string.resize(pos + len); if (pos > olen) { ByteList ptrByteList = ptr.string.getByteList(); // zero the gap Arrays.fill( ptrByteList.getUnsafeBytes(), ptrByteList.getBegin() + olen, ptrByteList.getBegin() + pos, (byte) 0); } } else { ptr.string.modify19(); } } @JRubyMethod(name = "putc", required = 1) public IRubyObject putc(IRubyObject ch) { checkWritable(); byte c = RubyNumeric.num2chr(ch); int olen; checkModifiable(); olen = ptr.string.size(); if ((ptr.flags & OpenFile.APPEND) != 0) { ptr.pos = olen; } strioExtend(ptr.pos, 1); ptr.string.getByteList().set(ptr.pos++, c); ptr.string.infectBy(this); return ch; } public static final ByteList NEWLINE = ByteList.create("\n"); @JRubyMethod(name = "puts", rest = true) public IRubyObject puts(ThreadContext context, IRubyObject[] args) { checkModifiable(); return puts(context, this, args); } private static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRubyObject[] args) { // TODO: This should defer to RubyIO logic, but we don't have puts right there for 1.9 Ruby runtime = context.runtime; if (args.length == 0) { RubyIO.write(context, maybeIO, RubyString.newStringShared(runtime, NEWLINE)); return runtime.getNil(); } for (int i = 0; i < args.length; i++) { RubyString line = null; if (!args[i].isNil()) { IRubyObject tmp = args[i].checkArrayType(); if (!tmp.isNil()) { RubyArray arr = (RubyArray) tmp; if (runtime.isInspecting(arr)) { line = runtime.newString("[...]"); } else { inspectPuts(context, maybeIO, arr); continue; } } else { if (args[i] instanceof RubyString) { line = (RubyString) args[i]; } else { line = args[i].asString(); } } } if (line != null) RubyIO.write(context, maybeIO, line); if (line == null || !line.getByteList().endsWith(NEWLINE)) { RubyIO.write(context, maybeIO, RubyString.newStringShared(runtime, NEWLINE)); } } return runtime.getNil(); } private static IRubyObject inspectPuts( ThreadContext context, IRubyObject maybeIO, RubyArray array) { Ruby runtime = context.runtime; try { runtime.registerInspecting(array); return puts(context, maybeIO, array.toJavaArray()); } finally { runtime.unregisterInspecting(array); } } // Make string based on internal data encoding (which ironically is its // external encoding. This seems messy and we should consider a more // uniform method for makeing strings (we have a slightly different variant // of this in RubyIO. private RubyString makeString(Ruby runtime, ByteList buf, boolean setEncoding) { if (runtime.is1_9() && setEncoding) buf.setEncoding(ptr.string.getEncoding()); RubyString str = RubyString.newString(runtime, buf); str.setTaint(true); return str; } private RubyString makeString(Ruby runtime, ByteList buf) { return makeString(runtime, buf, true); } @JRubyMethod(name = "read", optional = 2) public IRubyObject read(ThreadContext context, IRubyObject[] args) { checkReadable(); Ruby runtime = context.runtime; IRubyObject str = runtime.getNil(); int len; boolean binary = false; switch (args.length) { case 2: str = args[1]; if (!str.isNil()) { str = str.convertToString(); ((RubyString) str).modify(); } case 1: if (!args[0].isNil()) { len = RubyNumeric.fix2int(args[0]); if (len < 0) { throw getRuntime().newArgumentError("negative length " + len + " given"); } if (len > 0 && isEndOfString()) { if (!str.isNil()) ((RubyString) str).resize(0); return getRuntime().getNil(); } binary = true; break; } case 0: len = ptr.string.size(); if (len <= ptr.pos) { if (str.isNil()) { str = runtime.newString(); } else { ((RubyString) str).resize(0); } return str; } else { len -= ptr.pos; } break; default: throw getRuntime().newArgumentError(args.length, 0); } if (str.isNil()) { str = strioSubstr(runtime, ptr.pos, len); if (binary) ((RubyString) str).setEncoding(ASCIIEncoding.INSTANCE); } else { int rest = ptr.string.size() - ptr.pos; if (len > rest) len = rest; ((RubyString) str).resize(len); ByteList strByteList = ((RubyString) str).getByteList(); byte[] strBytes = strByteList.getUnsafeBytes(); ByteList dataByteList = ptr.string.getByteList(); byte[] dataBytes = dataByteList.getUnsafeBytes(); System.arraycopy( dataBytes, dataByteList.getBegin() + ptr.pos, strBytes, strByteList.getBegin(), len); if (binary) { ((RubyString) str).setEncoding(ASCIIEncoding.INSTANCE); } else { ((RubyString) str).setEncoding(ptr.string.getEncoding()); } } ptr.pos += ((RubyString) str).size(); return str; } @JRubyMethod(name = "read_nonblock", optional = 2) public IRubyObject read_nonblock(ThreadContext context, IRubyObject[] args) { // TODO: nonblock exception option IRubyObject val = read(context, args); if (val.isNil()) { throw context.runtime.newEOFError(); } return val; } @JRubyMethod(name = "readchar") public IRubyObject readchar(ThreadContext context) { IRubyObject c = callMethod(context, "getc"); if (c.isNil()) throw getRuntime().newEOFError(); return c; } @JRubyMethod(name = "readbyte") public IRubyObject readbyte(ThreadContext context) { IRubyObject c = callMethod(context, "getbyte"); if (c.isNil()) throw getRuntime().newEOFError(); return c; } @JRubyMethod(name = "readline", optional = 1, writes = FrameField.LASTLINE) public IRubyObject readline(ThreadContext context, IRubyObject[] args) { IRubyObject line = callMethod(context, "gets", args); if (line.isNil()) throw getRuntime().newEOFError(); return line; } @JRubyMethod(name = "readlines", optional = 2) public IRubyObject readlines(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; if (args.length > 0 && !args[args.length - 1].isNil() && args[args.length - 1].checkStringType19().isNil() && RubyNumeric.num2long(args[args.length - 1]) == 0) { throw runtime.newArgumentError("invalid limit: 0 for each_line"); } RubyArray ary = runtime.newArray(); IRubyObject line; checkReadable(); while (!(line = getline(context, args)).isNil()) { ary.append(line); } return ary; } @JRubyMethod(name = "reopen", required = 0, optional = 2) public IRubyObject reopen(ThreadContext context, IRubyObject[] args) { checkFrozen(); if (args.length == 1 && !(args[0] instanceof RubyString)) { return initialize_copy(context, args[0]); } // reset the state strioInit(context, args); return this; } @JRubyMethod(name = "rewind") public IRubyObject rewind(ThreadContext context) { checkInitialized(); this.ptr.pos = 0; this.ptr.lineno = 0; return RubyFixnum.zero(context.runtime); } @JRubyMethod(required = 1, optional = 1) public IRubyObject seek(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; checkFrozen(); checkFinalized(); int offset = RubyNumeric.num2int(args[0]); IRubyObject whence = context.nil; if (args.length > 1 && !args[0].isNil()) whence = args[1]; checkOpen(); switch (whence.isNil() ? 0 : RubyNumeric.num2int(whence)) { case 0: break; case 1: offset += ptr.pos; break; case 2: offset += ptr.string.size(); break; default: throw runtime.newErrnoEINVALError("invalid whence"); } if (offset < 0) throw runtime.newErrnoEINVALError("invalid seek value"); ptr.pos = offset; return RubyFixnum.zero(runtime); } @JRubyMethod(name = "string=", required = 1) public IRubyObject set_string(IRubyObject arg) { checkFrozen(); ptr.flags &= ~OpenFile.READWRITE; RubyString str = arg.convertToString(); ptr.flags = str.isFrozen() ? OpenFile.READABLE : OpenFile.READWRITE; ptr.pos = 0; ptr.lineno = 0; return ptr.string = str; } @JRubyMethod(name = "string") public IRubyObject string(ThreadContext context) { if (ptr.string == null) return context.nil; return ptr.string; } @JRubyMethod(name = "sync") public IRubyObject sync(ThreadContext context) { checkInitialized(); return context.runtime.getTrue(); } @JRubyMethod( name = {"sysread", "readpartial"}, optional = 2) public IRubyObject sysread(ThreadContext context, IRubyObject[] args) { IRubyObject val = callMethod(context, "read", args); if (val.isNil()) throw getRuntime().newEOFError(); return val; } // only here for the fake-out class in org.jruby public IRubyObject sysread(IRubyObject[] args) { return sysread(getRuntime().getCurrentContext(), args); } @JRubyMethod(name = "truncate", required = 1) public IRubyObject truncate(IRubyObject len) { checkWritable(); int l = RubyFixnum.fix2int(len); int plen = ptr.string.size(); if (l < 0) { throw getRuntime().newErrnoEINVALError("negative legnth"); } ptr.string.resize(l); ByteList buf = ptr.string.getByteList(); if (plen < l) { // zero the gap Arrays.fill(buf.getUnsafeBytes(), buf.getBegin() + plen, buf.getBegin() + l, (byte) 0); } return len; } @JRubyMethod(name = "ungetc") public IRubyObject ungetc(ThreadContext context, IRubyObject arg) { // TODO: Not a line-by-line port. checkReadable(); return ungetbyte(context, arg); } private void ungetbyteCommon(int c) { ptr.string.modify(); ptr.pos--; ByteList bytes = ptr.string.getByteList(); if (isEndOfString()) bytes.length((int) ptr.pos + 1); if (ptr.pos == -1) { bytes.prepend((byte) c); ptr.pos = 0; } else { bytes.set((int) ptr.pos, c); } } private void ungetbyteCommon(RubyString ungetBytes) { ByteList ungetByteList = ungetBytes.getByteList(); int len = ungetByteList.getRealSize(); int start = ptr.pos; if (len == 0) return; ptr.string.modify(); if (len > ptr.pos) { start = 0; } else { start = ptr.pos - len; } ByteList bytes = ptr.string.getByteList(); if (isEndOfString()) bytes.length(Math.max(ptr.pos, len)); bytes.replace(start, ptr.pos - start, ungetBytes.getByteList()); ptr.pos = start; } @JRubyMethod public IRubyObject ungetbyte(ThreadContext context, IRubyObject arg) { // TODO: Not a line-by-line port. checkReadable(); if (arg.isNil()) return arg; checkModifiable(); if (arg instanceof RubyFixnum) { ungetbyteCommon(RubyNumeric.fix2int(arg)); } else { ungetbyteCommon(arg.convertToString()); } return context.nil; } @JRubyMethod(name = "syswrite", required = 1) public IRubyObject syswrite(ThreadContext context, IRubyObject arg) { return RubyIO.write(context, this, arg); } @JRubyMethod(name = "write_nonblock", required = 1, optional = 1) public IRubyObject syswrite_nonblock(ThreadContext context, IRubyObject[] args) { // TODO: handle opts? return syswrite(context, args[0]); } @JRubyMethod( name = {"write"}, required = 1) public IRubyObject write(ThreadContext context, IRubyObject arg) { checkWritable(); Ruby runtime = context.runtime; RubyString str = arg.asString(); int len, olen; Encoding enc, enc2; enc = ptr.string.getEncoding(); enc2 = str.getEncoding(); if (enc != enc2 && enc != EncodingUtils.ascii8bitEncoding(runtime) // this is a hack because we don't seem to handle incoming ASCII-8BIT properly in transcoder && enc2 != ASCIIEncoding.INSTANCE) { str = runtime.newString(Transcoder.strConvEnc(context, str.getByteList(), enc2, enc)); } len = str.size(); if (len == 0) return RubyFixnum.zero(runtime); checkModifiable(); olen = ptr.string.size(); if ((ptr.flags & OpenFile.APPEND) != 0) { ptr.pos = olen; } if (ptr.pos == olen // this is a hack because we don't seem to handle incoming ASCII-8BIT properly in transcoder && enc2 != ASCIIEncoding.INSTANCE) { EncodingUtils.encStrBufCat(runtime, ptr.string, str.getByteList(), enc); } else { strioExtend(ptr.pos, len); ByteList ptrByteList = ptr.string.getByteList(); System.arraycopy( str.getByteList().getUnsafeBytes(), str.getByteList().getBegin(), ptrByteList.getUnsafeBytes(), ptrByteList.begin + ptr.pos, len); ptr.string.infectBy(str); } ptr.string.infectBy(this); ptr.pos += len; return RubyFixnum.newFixnum(runtime, len); } @JRubyMethod public IRubyObject set_encoding(ThreadContext context, IRubyObject ext_enc) { Encoding enc; if (ext_enc.isNil()) { enc = EncodingUtils.defaultExternalEncoding(context.runtime); } else { enc = EncodingUtils.rbToEncoding(context, ext_enc); } if (ptr.string.getEncoding() != enc) { ptr.string.modify(); ptr.string.setEncoding(enc); } return this; } @JRubyMethod public IRubyObject set_encoding(ThreadContext context, IRubyObject enc, IRubyObject ignored) { return set_encoding(context, enc); } @JRubyMethod public IRubyObject set_encoding( ThreadContext context, IRubyObject enc, IRubyObject ignored1, IRubyObject ignored2) { return set_encoding(context, enc); } @JRubyMethod public IRubyObject external_encoding(ThreadContext context) { return context .runtime .getEncodingService() .convertEncodingToRubyEncoding(ptr.string.getEncoding()); } @JRubyMethod public IRubyObject internal_encoding(ThreadContext context) { return context.nil; } @JRubyMethod(name = "each_codepoint") public IRubyObject each_codepoint(ThreadContext context, Block block) { Ruby runtime = context.runtime; if (!block.isGiven()) return enumeratorize(runtime, this, "each_codepoint"); checkReadable(); Encoding enc = ptr.string.getEncoding(); byte[] unsafeBytes = ptr.string.getByteList().getUnsafeBytes(); int begin = ptr.string.getByteList().getBegin(); for (; ; ) { if (ptr.pos >= ptr.string.size()) { return this; } int c = StringSupport.codePoint(runtime, enc, unsafeBytes, begin + ptr.pos, unsafeBytes.length); int n = StringSupport.codeLength(runtime, enc, c); block.yield(context, runtime.newFixnum(c)); ptr.pos += n; } } @JRubyMethod(name = "codepoints") public IRubyObject codepoints(ThreadContext context, Block block) { Ruby runtime = context.runtime; runtime.getWarnings().warn("StringIO#codepoints is deprecated; use #each_codepoint"); if (!block.isGiven()) return enumeratorize(runtime, this, "each_codepoint"); return each_codepoint(context, block); } /* rb: check_modifiable */ public void checkFrozen() { super.checkFrozen(); checkInitialized(); } private boolean readable() { return (flags & STRIO_READABLE) != 0 && (ptr.flags & OpenFile.READABLE) != 0; } private boolean writable() { return (flags & STRIO_WRITABLE) != 0 && (ptr.flags & OpenFile.WRITABLE) != 0; } private boolean closed() { return !((flags & STRIO_READWRITE) != 0 && (ptr.flags & OpenFile.READWRITE) != 0); } /* rb: readable */ private void checkReadable() { checkInitialized(); if (!readable()) { throw getRuntime().newIOError("not opened for reading"); } } /* rb: writable */ private void checkWritable() { checkInitialized(); if (!writable()) { throw getRuntime().newIOError("not opened for writing"); } // Tainting here if we ever want it. (secure 4) } private void checkModifiable() { checkFrozen(); if (ptr.string.isFrozen()) throw getRuntime().newIOError("not modifiable string"); } private void checkInitialized() { if (ptr == null) { throw getRuntime().newIOError("uninitialized stream"); } } private void checkFinalized() { if (ptr.string == null) { throw getRuntime().newIOError("not opened"); } } private void checkOpen() { if (closed()) { throw getRuntime().newIOError("closed stream"); } } }
public int parseString(RubyYaccLexer lexer, LexerSource src) throws java.io.IOException { boolean spaceSeen = false; int c; // FIXME: How much more obtuse can this be? // Heredoc already parsed this and saved string...Do not parse..just return if (flags == -1) { lexer.setValue(new Token("\"", lexer.getPosition())); return Tokens.tSTRING_END; } c = src.read(); if ((flags & RubyYaccLexer.STR_FUNC_QWORDS) != 0 && Character.isWhitespace(c)) { do { c = src.read(); } while (Character.isWhitespace(c)); spaceSeen = true; } if (c == end && nest == 0) { if ((flags & RubyYaccLexer.STR_FUNC_QWORDS) != 0) { flags = -1; lexer.getPosition(); return ' '; } if ((flags & RubyYaccLexer.STR_FUNC_REGEXP) != 0) { lexer.setValue( new RegexpNode(src.getPosition(), ByteList.create(""), parseRegexpFlags(src))); return Tokens.tREGEXP_END; } lexer.setValue(new Token("\"", lexer.getPosition())); return Tokens.tSTRING_END; } if (spaceSeen) { src.unread(c); lexer.getPosition(); return ' '; } // Single-quote fast path if (begin == '\0' && flags == 0) { ByteList buffer = new ByteList(); src.unread(c); if (parseSimpleStringIntoBuffer(src, buffer) == RubyYaccLexer.EOF) { throw new SyntaxException( PID.STRING_HITS_EOF, src.getPosition(), src.getCurrentLine(), "unterminated string meets end of file"); } /* ByteList buffer; src.unread(c); if ((buffer = src.readUntil(end)) == null) { throw new SyntaxException(src.getPosition(), "unterminated string meets end of file"); } */ lexer.setValue(new StrNode(lexer.getPosition(), buffer)); return Tokens.tSTRING_CONTENT; } ByteList buffer = new ByteList(); if ((flags & RubyYaccLexer.STR_FUNC_EXPAND) != 0 && c == '#') { c = src.read(); switch (c) { case '$': case '@': src.unread(c); lexer.setValue(new Token("#" + c, lexer.getPosition())); return Tokens.tSTRING_DVAR; case '{': lexer.setValue(new Token("#" + c, lexer.getPosition())); return Tokens.tSTRING_DBEG; } buffer.append((byte) '#'); } src.unread(c); if (parseStringIntoBuffer(lexer, src, buffer) == RubyYaccLexer.EOF) { throw new SyntaxException( PID.STRING_HITS_EOF, src.getPosition(), src.getCurrentLine(), "unterminated string meets end of file"); } lexer.setValue(new StrNode(lexer.getPosition(), buffer)); return Tokens.tSTRING_CONTENT; }
public final class EncodingService { private final CaseInsensitiveBytesHash<Entry> encodings; private final CaseInsensitiveBytesHash<Entry> aliases; // for fast lookup: encoding entry => org.jruby.RubyEncoding private final IRubyObject[] encodingList; // for fast lookup: org.joni.encoding.Encoding => org.jruby.RubyEncoding private RubyEncoding[] encodingIndex = new RubyEncoding[4]; // the runtime private final Ruby runtime; private final Encoding ascii8bit; private final Encoding javaDefault; private static final ByteList LOCALE_BL = ByteList.create("locale"); private static final ByteList EXTERNAL_BL = ByteList.create("external"); private static final ByteList INTERNAL_BL = ByteList.create("internal"); private static final ByteList FILESYSTEM_BL = ByteList.create("filesystem"); public EncodingService(Ruby runtime) { this.runtime = runtime; encodings = EncodingDB.getEncodings(); aliases = EncodingDB.getAliases(); ascii8bit = encodings.get("ASCII-8BIT".getBytes()).getEncoding(); Charset javaDefaultCharset = Charset.defaultCharset(); ByteList javaDefaultBL = new ByteList(javaDefaultCharset.name().getBytes()); Entry javaDefaultEntry = findEncodingOrAliasEntry(javaDefaultBL); javaDefault = javaDefaultEntry == null ? ascii8bit : javaDefaultEntry.getEncoding(); encodingList = new IRubyObject[encodings.size()]; if (runtime.is1_9()) { RubyEncoding.createEncodingClass(runtime); RubyConverter.createConverterClass(runtime); defineEncodings(); defineAliases(); // External should always have a value, but Encoding.external_encoding{,=} will lazily setup String encoding = runtime.getInstanceConfig().getExternalEncoding(); if (encoding != null && !encoding.equals("")) { Encoding loadedEncoding = loadEncoding(ByteList.create(encoding)); if (loadedEncoding == null) throw new MainExitException(1, "unknown encoding name - " + encoding); runtime.setDefaultExternalEncoding(loadedEncoding); } else { Encoding consoleEncoding = getConsoleEncoding(); Encoding availableEncoding = consoleEncoding == null ? getLocaleEncoding() : consoleEncoding; runtime.setDefaultExternalEncoding(availableEncoding); } encoding = runtime.getInstanceConfig().getInternalEncoding(); if (encoding != null && !encoding.equals("")) { Encoding loadedEncoding = loadEncoding(ByteList.create(encoding)); if (loadedEncoding == null) throw new MainExitException(1, "unknown encoding name - " + encoding); runtime.setDefaultInternalEncoding(loadedEncoding); } } } /** * Since Java 1.6, class {@link java.io.Console} is available. But the encoding or codepage of the * underlying connected console is currently private. Had to use Reflection to get it. * * @return console codepage */ private Encoding getConsoleEncoding() { if (!Platform.IS_WINDOWS) return null; Encoding consoleEncoding = null; try { Console console = System.console(); if (console != null) { final String CONSOLE_CHARSET = "cs"; Field fcs = Console.class.getDeclaredField(CONSOLE_CHARSET); fcs.setAccessible(true); Charset cs = (Charset) fcs.get(console); consoleEncoding = loadEncoding(ByteList.create(cs.name())); } } catch (Throwable e) { // to cover both Exceptions and Errors // just fall back on local encoding above } return consoleEncoding; } public Encoding getAscii8bitEncoding() { return ascii8bit; } public CaseInsensitiveBytesHash<Entry> getEncodings() { return encodings; } public CaseInsensitiveBytesHash<Entry> getAliases() { return aliases; } public Entry findEncodingEntry(ByteList bytes) { return encodings.get( bytes.getUnsafeBytes(), bytes.getBegin(), bytes.getBegin() + bytes.getRealSize()); } public Entry findAliasEntry(ByteList bytes) { return aliases.get( bytes.getUnsafeBytes(), bytes.getBegin(), bytes.getBegin() + bytes.getRealSize()); } public Entry findEncodingOrAliasEntry(ByteList bytes) { Entry e = findEncodingEntry(bytes); return e != null ? e : findAliasEntry(bytes); } // rb_locale_charmap...mostly public Encoding getLocaleEncoding() { Entry entry = findEncodingOrAliasEntry(new ByteList(Charset.defaultCharset().name().getBytes())); return entry == null ? ASCIIEncoding.INSTANCE : entry.getEncoding(); } public IRubyObject[] getEncodingList() { return encodingList; } public Encoding loadEncoding(ByteList name) { Entry entry = findEncodingOrAliasEntry(name); if (entry == null) return null; Encoding enc = entry.getEncoding(); // load the encoding int index = enc.getIndex(); if (index >= encodingIndex.length) { RubyEncoding tmp[] = new RubyEncoding[index + 4]; System.arraycopy(encodingIndex, 0, tmp, 0, encodingIndex.length); encodingIndex = tmp; } encodingIndex[index] = (RubyEncoding) encodingList[entry.getIndex()]; return enc; } public RubyEncoding getEncoding(Encoding enc) { int index = enc.getIndex(); RubyEncoding rubyEncoding; if (index < encodingIndex.length && (rubyEncoding = encodingIndex[index]) != null) { return rubyEncoding; } enc = loadEncoding(new ByteList(enc.getName(), false)); return encodingIndex[enc.getIndex()]; } private void defineEncodings() { HashEntryIterator hei = encodings.entryIterator(); while (hei.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) hei.next()); Entry ee = e.value; RubyEncoding encoding = RubyEncoding.newEncoding(runtime, e.bytes, e.p, e.end, ee.isDummy()); encodingList[ee.getIndex()] = encoding; defineEncodingConstants(runtime, encoding, e.bytes, e.p, e.end); } } private void defineAliases() { HashEntryIterator hei = aliases.entryIterator(); while (hei.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) hei.next()); Entry ee = e.value; RubyEncoding encoding = (RubyEncoding) encodingList[ee.getIndex()]; defineEncodingConstants(runtime, encoding, e.bytes, e.p, e.end); } } private void defineEncodingConstants( Ruby runtime, RubyEncoding encoding, byte[] name, int p, int end) { Encoding enc = ASCIIEncoding.INSTANCE; int s = p; int code = name[s] & 0xff; if (enc.isDigit(code)) return; boolean hasUpper = false; boolean hasLower = false; if (enc.isUpper(code)) { hasUpper = true; while (++s < end && (enc.isAlnum(name[s] & 0xff) || name[s] == (byte) '_')) { if (enc.isLower(name[s] & 0xff)) hasLower = true; } } boolean isValid = false; if (s >= end) { isValid = true; defineEncodingConstant(runtime, encoding, name, p, end); } if (!isValid || hasLower) { if (!hasLower || !hasUpper) { do { code = name[s] & 0xff; if (enc.isLower(code)) hasLower = true; if (enc.isUpper(code)) hasUpper = true; } while (++s < end && (!hasLower || !hasUpper)); } byte[] constName = new byte[end - p]; System.arraycopy(name, p, constName, 0, end - p); s = 0; code = constName[s] & 0xff; if (!isValid) { if (enc.isLower(code)) constName[s] = AsciiTables.ToUpperCaseTable[code]; for (; s < constName.length; ++s) { if (!enc.isAlnum(constName[s] & 0xff)) constName[s] = (byte) '_'; } if (hasUpper) { defineEncodingConstant(runtime, encoding, constName, 0, constName.length); } } if (hasLower) { for (s = 0; s < constName.length; ++s) { code = constName[s] & 0xff; if (enc.isLower(code)) constName[s] = AsciiTables.ToUpperCaseTable[code]; } defineEncodingConstant(runtime, encoding, constName, 0, constName.length); } } } private void defineEncodingConstant( Ruby runtime, RubyEncoding encoding, byte[] constName, int constP, int constEnd) { runtime.getEncoding().defineConstant(new String(constName, constP, constEnd), encoding); } public IRubyObject getDefaultExternal() { IRubyObject defaultExternal = convertEncodingToRubyEncoding(runtime.getDefaultExternalEncoding()); if (defaultExternal.isNil()) { // TODO: MRI seems to default blindly to US-ASCII and we were using Charset default from // Java...which is right? ByteList encodingName = ByteList.create("US-ASCII"); Encoding encoding = runtime.getEncodingService().loadEncoding(encodingName); runtime.setDefaultExternalEncoding(encoding); defaultExternal = convertEncodingToRubyEncoding(encoding); } return defaultExternal; } public IRubyObject getDefaultInternal() { return convertEncodingToRubyEncoding(runtime.getDefaultInternalEncoding()); } public IRubyObject convertEncodingToRubyEncoding(Encoding defaultEncoding) { return defaultEncoding != null ? getEncoding(defaultEncoding) : runtime.getNil(); } public Encoding getJavaDefault() { return javaDefault; } public Encoding getEncodingFromObject(IRubyObject arg) { if (arg == null) return null; Encoding encoding = null; if (arg instanceof RubyEncoding) { encoding = ((RubyEncoding) arg).getEncoding(); } else if (arg instanceof RubyFixnum && RubyNKF.NKFCharsetMap.containsKey((int) arg.convertToInteger().getLongValue())) { return getEncodingFromNKFId(arg); } else if (!arg.isNil()) { encoding = arg.convertToString().toEncoding(runtime); } return encoding; } private Encoding getEncodingFromNKFId(IRubyObject id) { String name = RubyNKF.NKFCharsetMap.get((int) id.convertToInteger().getLongValue()); HashEntryIterator hei = encodings.entryIterator(); while (hei.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) hei.next()); EncodingDB.Entry ee = e.value; String className = ee.getEncodingClass(); if (className.equals(name)) { Encoding enc = ee.getEncoding(); return enc; } } return null; } public Encoding getEncodingFromString(String string) { if (string == null) return null; ByteList name = new ByteList(ByteList.plain(string)); checkAsciiEncodingName(name); SpecialEncoding special = SpecialEncoding.valueOf(name); if (special != null) { return special.toEncoding(runtime); } return findEncodingWithError(name); } /** * Find an encoding given a Ruby object, coercing it to a String in the process. * * @param str the object to coerce and use to look up encoding. The coerced String must be * ASCII-compatible. * @return the Encoding object found, nil (for internal), or raises ArgumentError */ public Encoding findEncoding(IRubyObject str) { ByteList name = str.convertToString().getByteList(); checkAsciiEncodingName(name); SpecialEncoding special = SpecialEncoding.valueOf(name); if (special != null) { return special.toEncoding(runtime); } return findEncodingWithError(name); } /** * Find an encoding given a Ruby object, coercing it to a String in the process. * * @param str the object to coerce and use to look up encoding. The coerced String must be * ASCII-compatible. * @return the Encoding object found, nil (for internal), or raises ArgumentError */ public Entry findEntry(IRubyObject str) { ByteList name = str.convertToString().getByteList(); checkAsciiEncodingName(name); SpecialEncoding special = SpecialEncoding.valueOf(name); if (special != null) { return findEntryFromEncoding(special.toEncoding(runtime)); } return findEntryWithError(name); } /** * Look up the pre-existing RubyEncoding object for an EncodingDB.Entry. * * @param str * @return */ public IRubyObject rubyEncodingFromObject(IRubyObject str) { Entry entry = findEntry(str); if (entry == null) return runtime.getNil(); return getEncodingList()[entry.getIndex()]; } /** * Get a java.nio Charset for the given encoding, or null if impossible * * @param encoding the encoding * @return the charset */ public Charset charsetForEncoding(Encoding encoding) { Charset charset = encoding.getCharset(); if (encoding.toString().equals("ASCII-8BIT")) { return Charset.forName("ASCII"); } try { return Charset.forName(encoding.toString()); } catch (UnsupportedCharsetException uce) { throw runtime.newEncodingCompatibilityError( "no java.nio.charset.Charset found for encoding `" + encoding.toString() + "'"); } } private void checkAsciiEncodingName(ByteList name) { if (!name.getEncoding().isAsciiCompatible()) { throw runtime.newArgumentError("invalid name encoding (non ASCII)"); } } /** * Represents one of the four "special" internal encoding names: internal, external, locale, or * filesystem. */ private enum SpecialEncoding { LOCALE, EXTERNAL, INTERNAL, FILESYSTEM; public static SpecialEncoding valueOf(ByteList name) { if (name.caseInsensitiveCmp(LOCALE_BL) == 0) { return LOCALE; } else if (name.caseInsensitiveCmp(EXTERNAL_BL) == 0) { return EXTERNAL; } else if (name.caseInsensitiveCmp(INTERNAL_BL) == 0) { return INTERNAL; } else if (name.caseInsensitiveCmp(FILESYSTEM_BL) == 0) { return FILESYSTEM; } return null; } public Encoding toEncoding(Ruby runtime) { EncodingService service = runtime.getEncodingService(); switch (this) { case LOCALE: return service.getLocaleEncoding(); case EXTERNAL: return runtime.getDefaultExternalEncoding(); case INTERNAL: return runtime.getDefaultInternalEncoding(); case FILESYSTEM: // This needs to do something different on Windows. See encoding.c, // in the enc_set_filesystem_encoding function. return runtime.getDefaultExternalEncoding(); default: throw new RuntimeException("invalid SpecialEncoding: " + this); } } } /** * Find a non-special encoding, raising argument error if it does not exist. * * @param name the name of the encoding to look up * @return the Encoding object found, or raises ArgumentError */ public Encoding findEncodingWithError(ByteList name) { return findEntryWithError(name).getEncoding(); } /** * Find a non-special encoding Entry, raising argument error if it does not exist. * * @param name the name of the encoding to look up * @return the EncodingDB.Entry object found, or raises ArgumentError */ private Entry findEntryWithError(ByteList name) { Entry e = findEncodingOrAliasEntry(name); if (e == null) throw runtime.newArgumentError("unknown encoding name - " + name); return e; } private Entry findEntryFromEncoding(Encoding e) { if (e == null) return null; return findEncodingEntry(new ByteList(e.getName())); } }
@JRubyClass(name = "StringIO") @SuppressWarnings("deprecation") public class RubyStringIO extends org.jruby.RubyStringIO { static class StringIOData { long pos = 0L; int lineno = 0; boolean eof = false; boolean closedRead = false; boolean closedWrite = false; ModeFlags modes; /** * ATTN: the value of internal might be reset to null (during StringIO.open with block), so * watch out for that. */ RubyString internal; } StringIOData data; private static ObjectAllocator STRINGIO_ALLOCATOR = new ObjectAllocator() { @Override public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new RubyStringIO(runtime, klass); } }; public static RubyClass createStringIOClass(final Ruby runtime) { RubyClass stringIOClass = runtime.defineClass("StringIO", runtime.getClass("Data"), STRINGIO_ALLOCATOR); stringIOClass.defineAnnotatedMethods(RubyStringIO.class); stringIOClass.includeModule(runtime.getEnumerable()); if (runtime.getObject().isConstantDefined("Java")) { stringIOClass.defineAnnotatedMethods(IOJavaAddons.AnyIO.class); } return stringIOClass; } @JRubyMethod(meta = true, rest = true) public static IRubyObject open( ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { RubyStringIO strio = (RubyStringIO) ((RubyClass) recv).newInstance(context, args, Block.NULL_BLOCK); IRubyObject val = strio; if (block.isGiven()) { try { val = block.yield(context, strio); } finally { strio.doFinalize(); } } return val; } protected RubyStringIO(Ruby runtime, RubyClass klass) { super(runtime, klass); data = new StringIOData(); } private void initializeModes(Object modeArgument) { Ruby runtime = getRuntime(); if (modeArgument == null) { data.modes = RubyIO.newModeFlags(runtime, "r+"); } else if (modeArgument instanceof Long) { data.modes = RubyIO.newModeFlags(runtime, ((Long) modeArgument).longValue()); } else { data.modes = RubyIO.newModeFlags(runtime, (String) modeArgument); } setupModes(); } @JRubyMethod(optional = 2, visibility = PRIVATE) @Override public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) { Object modeArgument = null; Ruby runtime = getRuntime(); switch (args.length) { case 0: data.internal = runtime.is1_9() ? RubyString.newEmptyString(runtime, runtime.getDefaultExternalEncoding()) : RubyString.newEmptyString(getRuntime()); modeArgument = "r+"; break; case 1: data.internal = args[0].convertToString(); modeArgument = data.internal.isFrozen() ? "r" : "r+"; break; case 2: data.internal = args[0].convertToString(); if (args[1] instanceof RubyFixnum) { modeArgument = RubyFixnum.fix2long(args[1]); } else { modeArgument = args[1].convertToString().toString(); } break; } initializeModes(modeArgument); if (data.modes.isWritable() && data.internal.isFrozen()) { throw getRuntime().newErrnoEACCESError("Permission denied"); } if (data.modes.isTruncate()) { data.internal.modifyCheck(); data.internal.empty(); } return this; } @JRubyMethod(visibility = PRIVATE) @Override public IRubyObject initialize_copy(IRubyObject other) { RubyStringIO otherIO = (RubyStringIO) TypeConverter.convertToType(other, getRuntime().getClass("StringIO"), "to_strio"); if (this == otherIO) return this; data = otherIO.data; if (otherIO.isTaint()) setTaint(true); return this; } @JRubyMethod(name = "<<", required = 1) @Override public IRubyObject append(ThreadContext context, IRubyObject arg) { writeInternal(context, arg); return this; } @JRubyMethod @Override public IRubyObject binmode() { return this; } @JRubyMethod @Override public IRubyObject close() { checkInitialized(); checkOpen(); data.closedRead = true; data.closedWrite = true; return getRuntime().getNil(); } private void doFinalize() { data.closedRead = true; data.closedWrite = true; data.internal = null; } @JRubyMethod(name = "closed?") @Override public IRubyObject closed_p() { checkInitialized(); return getRuntime().newBoolean(data.closedRead && data.closedWrite); } @JRubyMethod @Override public IRubyObject close_read() { checkReadable(); data.closedRead = true; return getRuntime().getNil(); } @JRubyMethod(name = "closed_read?") @Override public IRubyObject closed_read_p() { checkInitialized(); return getRuntime().newBoolean(data.closedRead); } @JRubyMethod @Override public IRubyObject close_write() { checkWritable(); data.closedWrite = true; return getRuntime().getNil(); } @JRubyMethod(name = "closed_write?") @Override public IRubyObject closed_write_p() { checkInitialized(); return getRuntime().newBoolean(data.closedWrite); } @Override public IRubyObject eachInternal(ThreadContext context, IRubyObject[] args, Block block) { IRubyObject line = getsOnly(context, args); while (!line.isNil()) { block.yield(context, line); line = getsOnly(context, args); } return this; } @JRubyMethod(name = "each", optional = 1, writes = FrameField.LASTLINE) @Override public IRubyObject each(ThreadContext context, IRubyObject[] args, Block block) { return block.isGiven() ? eachInternal(context, args, block) : enumeratorize(context.runtime, this, "each", args); } @JRubyMethod(optional = 1) @Override public IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block block) { return block.isGiven() ? eachInternal(context, args, block) : enumeratorize(context.runtime, this, "each_line", args); } @JRubyMethod(optional = 1) @Override public IRubyObject lines(ThreadContext context, IRubyObject[] args, Block block) { return block.isGiven() ? each(context, args, block) : enumeratorize(context.runtime, this, "lines", args); } @Override public IRubyObject each_byte(ThreadContext context, Block block) { checkReadable(); Ruby runtime = context.runtime; ByteList bytes = data.internal.getByteList(); // Check the length every iteration, since // the block can modify this string. while (data.pos < bytes.length()) { block.yield(context, runtime.newFixnum(bytes.get((int) data.pos++) & 0xFF)); } return this; } @JRubyMethod(name = "each_byte") @Override public IRubyObject each_byte19(ThreadContext context, Block block) { return block.isGiven() ? each_byte(context, block) : enumeratorize(context.runtime, this, "each_byte"); } @JRubyMethod @Override public IRubyObject bytes(ThreadContext context, Block block) { return block.isGiven() ? each_byte(context, block) : enumeratorize(context.runtime, this, "bytes"); } @Override public IRubyObject each_charInternal(final ThreadContext context, final Block block) { checkReadable(); Ruby runtime = context.runtime; ByteList bytes = data.internal.getByteList(); int len = bytes.getRealSize(); int end = bytes.getBegin() + len; Encoding enc = runtime.is1_9() ? bytes.getEncoding() : runtime.getKCode().getEncoding(); while (data.pos < len) { int pos = (int) data.pos; int n = StringSupport.length(enc, bytes.getUnsafeBytes(), pos, end); if (len < pos + n) n = len - pos; data.pos += n; block.yield(context, data.internal.makeShared19(runtime, pos, n)); } return this; } @JRubyMethod @Override public IRubyObject each_char(final ThreadContext context, final Block block) { return block.isGiven() ? each_charInternal(context, block) : enumeratorize(context.runtime, this, "each_char"); } @JRubyMethod @Override public IRubyObject chars(final ThreadContext context, final Block block) { return block.isGiven() ? each_charInternal(context, block) : enumeratorize(context.runtime, this, "chars"); } @JRubyMethod(name = {"eof", "eof?"}) @Override public IRubyObject eof() { return getRuntime().newBoolean(isEOF()); } private boolean isEOF() { return isEndOfString() || data.eof; } private boolean isEndOfString() { return data.pos >= data.internal.getByteList().length(); } @JRubyMethod(name = "fcntl") @Override public IRubyObject fcntl() { throw getRuntime().newNotImplementedError("fcntl not implemented"); } @JRubyMethod(name = "fileno") @Override public IRubyObject fileno() { return getRuntime().getNil(); } @JRubyMethod(name = "flush") @Override public IRubyObject flush() { return this; } @JRubyMethod(name = "fsync") @Override public IRubyObject fsync() { return RubyFixnum.zero(getRuntime()); } @JRubyMethod(name = {"getc", "getbyte"}) @Override public IRubyObject getc() { checkReadable(); if (isEndOfString()) return getRuntime().getNil(); return getRuntime().newFixnum(data.internal.getByteList().get((int) data.pos++) & 0xFF); } @JRubyMethod(name = "getc", compat = CompatVersion.RUBY1_9) @Override public IRubyObject getc19(ThreadContext context) { checkReadable(); if (isEndOfString()) return context.runtime.getNil(); return context.runtime.newString( "" + (char) (data.internal.getByteList().get((int) data.pos++) & 0xFF)); } private IRubyObject internalGets(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; if (!isEndOfString() && !data.eof) { boolean isParagraph = false; boolean is19 = runtime.is1_9(); ByteList sep = ((RubyString) runtime.getGlobalVariables().get("$/")).getByteList(); IRubyObject sepArg; int limit = -1; if (is19) { IRubyObject limitArg = (args.length > 1 ? args[1] : (args.length > 0 && args[0] instanceof RubyFixnum ? args[0] : null)); if (limitArg != null) { limit = RubyNumeric.fix2int(limitArg); } sepArg = (args.length > 0 && !(args[0] instanceof RubyFixnum) ? args[0] : null); } else { sepArg = (args.length > 0 ? args[0] : null); } if (sepArg != null) { if (sepArg.isNil()) { int bytesAvailable = data.internal.getByteList().getRealSize() - (int) data.pos; int bytesToUse = (limit < 0 || limit >= bytesAvailable ? bytesAvailable : limit); ByteList buf = data.internal.getByteList().makeShared((int) data.pos, bytesToUse); data.pos += buf.getRealSize(); return makeString(runtime, buf); } sep = sepArg.convertToString().getByteList(); if (sep.getRealSize() == 0) { isParagraph = true; sep = Stream.PARAGRAPH_SEPARATOR; } } ByteList ss = data.internal.getByteList(); if (isParagraph) { swallowLF(ss); if (data.pos == ss.getRealSize()) { return runtime.getNil(); } } int ix = ss.indexOf(sep, (int) data.pos); ByteList add; if (-1 == ix) { ix = data.internal.getByteList().getRealSize(); add = ByteList.EMPTY_BYTELIST; } else { add = sep; } int bytes = ix - (int) data.pos; int bytesToUse = (limit < 0 || limit >= bytes ? bytes : limit); int bytesWithSep = ix - (int) data.pos + add.getRealSize(); int bytesToUseWithSep = (limit < 0 || limit >= bytesWithSep ? bytesWithSep : limit); ByteList line = new ByteList(bytesToUseWithSep); if (is19) line.setEncoding(data.internal.getByteList().getEncoding()); line.append(data.internal.getByteList(), (int) data.pos, bytesToUse); data.pos += bytesToUse; int sepBytesToUse = bytesToUseWithSep - bytesToUse; line.append(add, 0, sepBytesToUse); data.pos += sepBytesToUse; if (sepBytesToUse >= add.getRealSize()) { data.lineno++; } return makeString(runtime, line); } return runtime.getNil(); } private void swallowLF(ByteList list) { while (data.pos < list.getRealSize()) { if (list.get((int) data.pos) == '\n') { data.pos++; } else { break; } } } @JRubyMethod( name = "gets", optional = 1, writes = FrameField.LASTLINE, compat = CompatVersion.RUBY1_8) @Override public IRubyObject gets(ThreadContext context, IRubyObject[] args) { IRubyObject result = getsOnly(context, args); context.getCurrentScope().setLastLine(result); return result; } @JRubyMethod( name = "gets", optional = 2, writes = FrameField.LASTLINE, compat = CompatVersion.RUBY1_9) @Override public IRubyObject gets19(ThreadContext context, IRubyObject[] args) { IRubyObject result = getsOnly(context, args); context.getCurrentScope().setLastLine(result); return result; } @Override public IRubyObject getsOnly(ThreadContext context, IRubyObject[] args) { checkReadable(); return internalGets(context, args); } @JRubyMethod(name = {"tty?", "isatty"}) @Override public IRubyObject isatty() { return getRuntime().getFalse(); } @JRubyMethod(name = {"length", "size"}) @Override public IRubyObject length() { checkFinalized(); return getRuntime().newFixnum(data.internal.getByteList().length()); } @JRubyMethod(name = "lineno") @Override public IRubyObject lineno() { return getRuntime().newFixnum(data.lineno); } @JRubyMethod(name = "lineno=", required = 1) @Override public IRubyObject set_lineno(IRubyObject arg) { data.lineno = RubyNumeric.fix2int(arg); return getRuntime().getNil(); } @JRubyMethod(name = "path", compat = CompatVersion.RUBY1_8) @Override public IRubyObject path() { return getRuntime().getNil(); } @JRubyMethod(name = "pid") @Override public IRubyObject pid() { return getRuntime().getNil(); } @JRubyMethod(name = {"pos", "tell"}) @Override public IRubyObject pos() { return getRuntime().newFixnum(data.pos); } @JRubyMethod(name = "pos=", required = 1) @Override public IRubyObject set_pos(IRubyObject arg) { data.pos = RubyNumeric.fix2int(arg); if (data.pos < 0) throw getRuntime().newErrnoEINVALError("Invalid argument"); if (!isEndOfString()) data.eof = false; return getRuntime().getNil(); } @JRubyMethod(name = "print", rest = true) @Override public IRubyObject print(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; if (args.length != 0) { for (int i = 0, j = args.length; i < j; i++) { append(context, args[i]); } } else { IRubyObject arg = runtime.getGlobalVariables().get("$_"); append( context, arg.isNil() ? makeString(runtime, new ByteList(new byte[] {'n', 'i', 'l'})) : arg); } IRubyObject sep = runtime.getGlobalVariables().get("$\\"); if (!sep.isNil()) append(context, sep); return runtime.getNil(); } @JRubyMethod(name = "print", rest = true, compat = CompatVersion.RUBY1_9) @Override public IRubyObject print19(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; if (args.length != 0) { for (int i = 0, j = args.length; i < j; i++) { append(context, args[i]); } } else { IRubyObject arg = runtime.getGlobalVariables().get("$_"); append(context, arg.isNil() ? RubyString.newEmptyString(getRuntime()) : arg); } IRubyObject sep = runtime.getGlobalVariables().get("$\\"); if (!sep.isNil()) append(context, sep); return runtime.getNil(); } @JRubyMethod(name = "printf", required = 1, rest = true) @Override public IRubyObject printf(ThreadContext context, IRubyObject[] args) { append(context, RubyKernel.sprintf(context, this, args)); return getRuntime().getNil(); } @JRubyMethod(name = "putc", required = 1) @Override public IRubyObject putc(IRubyObject obj) { checkWritable(); byte c = RubyNumeric.num2chr(obj); checkFrozen(); data.internal.modify(); ByteList bytes = data.internal.getByteList(); if (data.modes.isAppendable()) { data.pos = bytes.length(); bytes.append(c); } else { if (isEndOfString()) bytes.length((int) data.pos + 1); bytes.set((int) data.pos, c); data.pos++; } return obj; } public static final ByteList NEWLINE = ByteList.create("\n"); @JRubyMethod(name = "puts", rest = true) @Override public IRubyObject puts(ThreadContext context, IRubyObject[] args) { checkWritable(); // FIXME: the code below is a copy of RubyIO.puts, // and we should avoid copy-paste. if (args.length == 0) { callMethod(context, "write", RubyString.newStringShared(getRuntime(), NEWLINE)); return getRuntime().getNil(); } for (int i = 0; i < args.length; i++) { RubyString line; if (args[i].isNil()) { line = getRuntime().newString("nil"); } else { IRubyObject tmp = args[i].checkArrayType(); if (!tmp.isNil()) { RubyArray arr = (RubyArray) tmp; if (getRuntime().isInspecting(arr)) { line = getRuntime().newString("[...]"); } else { inspectPuts(context, arr); continue; } } else { if (args[i] instanceof RubyString) { line = (RubyString) args[i]; } else { line = args[i].asString(); } } } callMethod(context, "write", line); if (!line.getByteList().endsWith(NEWLINE)) { callMethod(context, "write", RubyString.newStringShared(getRuntime(), NEWLINE)); } } return getRuntime().getNil(); } private IRubyObject inspectPuts(ThreadContext context, RubyArray array) { try { getRuntime().registerInspecting(array); return puts(context, array.toJavaArray()); } finally { getRuntime().unregisterInspecting(array); } } // Make string based on internal data encoding (which ironically is its // external encoding. This seems messy and we should consider a more // uniform method for makeing strings (we have a slightly different variant // of this in RubyIO. private RubyString makeString(Ruby runtime, ByteList buf) { if (runtime.is1_9()) buf.setEncoding(data.internal.getEncoding()); RubyString str = RubyString.newString(runtime, buf); str.setTaint(true); return str; } @SuppressWarnings("fallthrough") @JRubyMethod(name = "read", optional = 2) @Override public IRubyObject read(IRubyObject[] args) { checkReadable(); ByteList buf = null; int length = 0; int oldLength = 0; RubyString originalString = null; switch (args.length) { case 2: originalString = args[1].convertToString(); // must let original string know we're modifying, so shared buffers aren't damaged originalString.modify(); buf = originalString.getByteList(); case 1: if (!args[0].isNil()) { length = RubyNumeric.fix2int(args[0]); oldLength = length; if (length < 0) { throw getRuntime().newArgumentError("negative length " + length + " given"); } if (length > 0 && isEndOfString()) { data.eof = true; if (buf != null) buf.setRealSize(0); return getRuntime().getNil(); } else if (data.eof) { if (buf != null) buf.setRealSize(0); return getRuntime().getNil(); } break; } case 0: oldLength = -1; length = data.internal.getByteList().length(); if (length <= data.pos) { data.eof = true; if (buf == null) { buf = new ByteList(); } else { buf.setRealSize(0); } return makeString(getRuntime(), buf); } else { length -= data.pos; } break; default: getRuntime().newArgumentError(args.length, 0); } if (buf == null) { int internalLength = data.internal.getByteList().length(); if (internalLength > 0) { if (internalLength >= data.pos + length) { buf = new ByteList(data.internal.getByteList(), (int) data.pos, length); } else { int rest = (int) (data.internal.getByteList().length() - data.pos); if (length > rest) length = rest; buf = new ByteList(data.internal.getByteList(), (int) data.pos, length); } } } else { int rest = (int) (data.internal.getByteList().length() - data.pos); if (length > rest) length = rest; // Yow...this is still ugly byte[] target = buf.getUnsafeBytes(); if (target.length > length) { System.arraycopy( data.internal.getByteList().getUnsafeBytes(), (int) data.pos, target, 0, length); buf.setBegin(0); buf.setRealSize(length); } else { target = new byte[length]; System.arraycopy( data.internal.getByteList().getUnsafeBytes(), (int) data.pos, target, 0, length); buf.setBegin(0); buf.setRealSize(length); buf.setUnsafeBytes(target); } } if (buf == null) { if (!data.eof) buf = new ByteList(); length = 0; } else { length = buf.length(); data.pos += length; } if (oldLength < 0 || oldLength > length) data.eof = true; return originalString != null ? originalString : makeString(getRuntime(), buf); } @JRubyMethod(name = "read_nonblock", compat = CompatVersion.RUBY1_9, required = 1, optional = 1) @Override public IRubyObject read_nonblock(ThreadContext contet, IRubyObject[] args) { return sysreadCommon(args); } /** readpartial(length, [buffer]) */ @JRubyMethod(name = "readpartial", compat = CompatVersion.RUBY1_9, required = 1, optional = 1) @Override public IRubyObject readpartial(ThreadContext context, IRubyObject[] args) { return sysreadCommon(args); } @JRubyMethod(name = {"readchar", "readbyte"}) @Override public IRubyObject readchar() { IRubyObject c = getc(); if (c.isNil()) throw getRuntime().newEOFError(); return c; } @JRubyMethod(name = "readchar", compat = CompatVersion.RUBY1_9) @Override public IRubyObject readchar19(ThreadContext context) { IRubyObject c = getc19(context); if (c.isNil()) throw getRuntime().newEOFError(); return c; } @JRubyMethod(name = "readline", optional = 1, writes = FrameField.LASTLINE) @Override public IRubyObject readline(ThreadContext context, IRubyObject[] args) { IRubyObject line = gets(context, args); if (line.isNil()) throw getRuntime().newEOFError(); return line; } @JRubyMethod(name = "readlines", optional = 1) public IRubyObject readlines(ThreadContext context, IRubyObject[] arg) { checkReadable(); List<IRubyObject> lns = new ArrayList<IRubyObject>(); while (!(isEOF())) { IRubyObject line = internalGets(context, arg); if (line.isNil()) { break; } lns.add(line); } return getRuntime().newArray(lns); } @JRubyMethod(name = "reopen", required = 0, optional = 2) @Override public IRubyObject reopen(IRubyObject[] args) { if (args.length == 1 && !(args[0] instanceof RubyString)) { return initialize_copy(args[0]); } // reset the state doRewind(); data.closedRead = false; data.closedWrite = false; return initialize(args, Block.NULL_BLOCK); } @JRubyMethod(name = "rewind") @Override public IRubyObject rewind() { doRewind(); return RubyFixnum.zero(getRuntime()); } private void doRewind() { this.data.pos = 0L; this.data.eof = false; this.data.lineno = 0; } @JRubyMethod(required = 1, optional = 1) @Override public IRubyObject seek(IRubyObject[] args) { checkOpen(); checkFinalized(); long amount = RubyNumeric.num2long(args[0]); int whence = Stream.SEEK_SET; long newPosition = data.pos; if (args.length > 1 && !args[0].isNil()) whence = RubyNumeric.fix2int(args[1]); if (whence == Stream.SEEK_CUR) { newPosition += amount; } else if (whence == Stream.SEEK_END) { newPosition = data.internal.getByteList().length() + amount; } else if (whence == Stream.SEEK_SET) { newPosition = amount; } else { throw getRuntime().newErrnoEINVALError("invalid whence"); } if (newPosition < 0) throw getRuntime().newErrnoEINVALError("invalid seek value"); data.pos = newPosition; data.eof = false; return RubyFixnum.zero(getRuntime()); } @JRubyMethod(name = "string=", required = 1) @Override public IRubyObject set_string(IRubyObject arg) { return reopen(new IRubyObject[] {arg.convertToString()}); } @JRubyMethod(name = "sync=", required = 1) @Override public IRubyObject set_sync(IRubyObject args) { return args; } @JRubyMethod(name = "string") @Override public IRubyObject string() { if (data.internal == null) return getRuntime().getNil(); return data.internal; } @JRubyMethod(name = "sync") @Override public IRubyObject sync() { return getRuntime().getTrue(); } @JRubyMethod(name = "sysread", optional = 2) @Override public IRubyObject sysread(IRubyObject[] args) { return sysreadCommon(args); } private IRubyObject sysreadCommon(IRubyObject[] args) { IRubyObject obj = read(args); if (isEOF() && obj.isNil()) throw getRuntime().newEOFError(); return obj; } @JRubyMethod(name = "truncate", required = 1) @Override public IRubyObject truncate(IRubyObject arg) { checkWritable(); int len = RubyFixnum.fix2int(arg); if (len < 0) { throw getRuntime().newErrnoEINVALError("negative legnth"); } data.internal.modify(); ByteList buf = data.internal.getByteList(); if (len < buf.length()) { Arrays.fill(buf.getUnsafeBytes(), len, buf.length(), (byte) 0); } buf.length(len); return arg; } @JRubyMethod(name = "ungetc", required = 1) @Override public IRubyObject ungetc(IRubyObject arg) { checkReadable(); int c = RubyNumeric.num2int(arg); if (data.pos == 0) return getRuntime().getNil(); ungetcCommon(c); return getRuntime().getNil(); } @JRubyMethod(name = "ungetc", compat = CompatVersion.RUBY1_9) @Override public IRubyObject ungetc19(ThreadContext context, IRubyObject arg) { checkReadable(); if (!arg.isNil()) { int c; if (arg instanceof RubyFixnum) { c = RubyNumeric.fix2int(arg); } else { RubyString str = arg.convertToString(); c = str.getEncoding().mbcToCode(str.getBytes(), 0, 1); } ungetcCommon(c); } return getRuntime().getNil(); } private void ungetcCommon(int c) { data.internal.modify(); data.pos--; ByteList bytes = data.internal.getByteList(); if (isEndOfString()) bytes.length((int) data.pos + 1); bytes.set((int) data.pos, c); } @JRubyMethod( name = {"write", "syswrite"}, required = 1) @Override public IRubyObject write(ThreadContext context, IRubyObject arg) { return context.runtime.newFixnum(writeInternal(context, arg)); } private int writeInternal(ThreadContext context, IRubyObject arg) { checkWritable(); checkFrozen(); RubyString val = arg.asString(); data.internal.modify(); if (data.modes.isAppendable()) { data.internal.getByteList().append(val.getByteList()); data.pos = data.internal.getByteList().length(); } else { int left = data.internal.getByteList().length() - (int) data.pos; data.internal .getByteList() .replace((int) data.pos, Math.min(val.getByteList().length(), left), val.getByteList()); data.pos += val.getByteList().length(); } if (val.isTaint()) { data.internal.setTaint(true); } return val.getByteList().length(); } @JRubyMethod(compat = RUBY1_9) @Override public IRubyObject set_encoding(ThreadContext context, IRubyObject enc) { Encoding encoding = context.runtime.getEncodingService().getEncodingFromObject(enc); data.internal.setEncoding(encoding); return this; } @JRubyMethod(compat = RUBY1_9) @Override public IRubyObject external_encoding(ThreadContext context) { return context .runtime .getEncodingService() .convertEncodingToRubyEncoding(data.internal.getEncoding()); } @JRubyMethod(compat = RUBY1_9) @Override public IRubyObject internal_encoding(ThreadContext context) { return context.nil; } /* rb: check_modifiable */ @Override public void checkFrozen() { checkInitialized(); if (data.internal.isFrozen()) throw getRuntime().newIOError("not modifiable string"); } /* rb: readable */ private void checkReadable() { checkInitialized(); if (data.closedRead || !data.modes.isReadable()) { throw getRuntime().newIOError("not opened for reading"); } } /* rb: writable */ private void checkWritable() { checkInitialized(); if (data.closedWrite || !data.modes.isWritable()) { throw getRuntime().newIOError("not opened for writing"); } // Tainting here if we ever want it. (secure 4) } private void checkInitialized() { if (data.modes == null) { throw getRuntime().newIOError("uninitialized stream"); } } private void checkFinalized() { if (data.internal == null) { throw getRuntime().newIOError("not opened"); } } private void checkOpen() { if (data.closedRead && data.closedWrite) { throw getRuntime().newIOError("closed stream"); } } private void setupModes() { data.closedWrite = false; data.closedRead = false; if (data.modes.isReadOnly()) data.closedWrite = true; if (!data.modes.isReadable()) data.closedRead = true; } }
@JRubyClass(name = "Encoding") public class RubyEncoding extends RubyObject { public static final Charset UTF8 = Charset.forName("UTF-8"); public static final Charset ISO = Charset.forName("ISO-8859-1"); public static final ByteList LOCALE = ByteList.create("locale"); public static final ByteList EXTERNAL = ByteList.create("external"); public static RubyClass createEncodingClass(Ruby runtime) { RubyClass encodingc = runtime.defineClass( "Encoding", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR); runtime.setEncoding(encodingc); encodingc.index = ClassIndex.ENCODING; encodingc.setReifiedClass(RubyEncoding.class); encodingc.kindOf = new RubyModule.KindOf() { @Override public boolean isKindOf(IRubyObject obj, RubyModule type) { return obj instanceof RubyEncoding; } }; encodingc.getSingletonClass().undefineMethod("allocate"); encodingc.defineAnnotatedMethods(RubyEncoding.class); return encodingc; } private Encoding encoding; private final ByteList name; private final boolean isDummy; private RubyEncoding(Ruby runtime, byte[] name, int p, int end, boolean isDummy) { super(runtime, runtime.getEncoding()); this.name = new ByteList(name, p, end); this.isDummy = isDummy; } private RubyEncoding(Ruby runtime, byte[] name, boolean isDummy) { this(runtime, name, 0, name.length, isDummy); } private RubyEncoding(Ruby runtime, Encoding encoding) { super(runtime, runtime.getEncoding()); this.name = new ByteList(encoding.getName()); this.isDummy = false; this.encoding = encoding; } public static RubyEncoding newEncoding( Ruby runtime, byte[] name, int p, int end, boolean isDummy) { return new RubyEncoding(runtime, name, p, end, isDummy); } public static RubyEncoding newEncoding(Ruby runtime, byte[] name, boolean isDummy) { return new RubyEncoding(runtime, name, isDummy); } public static RubyEncoding newEncoding(Ruby runtime, Encoding encoding) { return new RubyEncoding(runtime, encoding); } public final Encoding getEncoding() { // TODO: make threadsafe if (encoding == null) encoding = getRuntime().getEncodingService().loadEncoding(name); return encoding; } public static Encoding areCompatible(IRubyObject obj1, IRubyObject obj2) { Encoding enc1 = null; Encoding enc2 = null; if (obj1 instanceof RubyEncoding) { enc1 = ((RubyEncoding) obj1).getEncoding(); } else if (obj1 instanceof EncodingCapable) { enc1 = ((EncodingCapable) obj1).getEncoding(); } if (obj2 instanceof RubyEncoding) { enc2 = ((RubyEncoding) obj2).getEncoding(); } else if (obj2 instanceof EncodingCapable) { enc2 = ((EncodingCapable) obj2).getEncoding(); } if (enc1 != null && enc2 != null) { if (enc1 == enc2) return enc1; if (obj2 instanceof RubyString && ((RubyString) obj2).getByteList().getRealSize() == 0) return enc1; if (obj1 instanceof RubyString && ((RubyString) obj1).getByteList().getRealSize() == 0) return enc2; if (!enc1.isAsciiCompatible() || !enc2.isAsciiCompatible()) return null; if (!(obj2 instanceof RubyString) && enc2 instanceof USASCIIEncoding) return enc1; if (!(obj1 instanceof RubyString) && enc1 instanceof USASCIIEncoding) return enc2; if (!(obj1 instanceof RubyString)) { IRubyObject objTmp = obj1; obj1 = obj2; obj1 = objTmp; Encoding encTmp = enc1; enc1 = enc2; enc2 = encTmp; } if (obj1 instanceof RubyString) { int cr1 = ((RubyString) obj1).scanForCodeRange(); if (obj2 instanceof RubyString) { int cr2 = ((RubyString) obj2).scanForCodeRange(); return areCompatible(enc1, cr1, enc2, cr2); } if (cr1 == StringSupport.CR_7BIT) return enc2; } } return null; } static Encoding areCompatible(Encoding enc1, int cr1, Encoding enc2, int cr2) { if (cr1 != cr2) { /* may need to handle ENC_CODERANGE_BROKEN */ if (cr1 == StringSupport.CR_7BIT) return enc2; if (cr2 == StringSupport.CR_7BIT) return enc1; } if (cr2 == StringSupport.CR_7BIT) { if (enc1 instanceof ASCIIEncoding) return enc2; return enc1; } if (cr1 == StringSupport.CR_7BIT) return enc2; return null; } public static byte[] encodeUTF8(CharSequence cs) { return getUTF8Coder().encode(cs); } public static byte[] encodeUTF8(String str) { return getUTF8Coder().encode(str); } public static byte[] encode(CharSequence cs, Charset charset) { ByteBuffer buffer = charset.encode(cs.toString()); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return bytes; } public static byte[] encode(String str, Charset charset) { ByteBuffer buffer = charset.encode(str); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return bytes; } public static String decodeUTF8(byte[] bytes, int start, int length) { return getUTF8Coder().decode(bytes, start, length); } public static String decodeUTF8(byte[] bytes) { return getUTF8Coder().decode(bytes); } public static String decode(byte[] bytes, int start, int length, Charset charset) { return charset.decode(ByteBuffer.wrap(bytes, start, length)).toString(); } public static String decode(byte[] bytes, Charset charset) { return charset.decode(ByteBuffer.wrap(bytes)).toString(); } private static class UTF8Coder { private final CharsetEncoder encoder = UTF8.newEncoder(); private final CharsetDecoder decoder = UTF8.newDecoder(); /** The maximum number of characters we can encode/decode in our cached buffers */ private static final int CHAR_THRESHOLD = 1024; /** * The resulting encode/decode buffer sized by the max number of characters (using 4 bytes per * char possible for utf-8) */ private static final int BUF_SIZE = CHAR_THRESHOLD * 4; private final ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); private final CharBuffer charBuffer = CharBuffer.allocate(BUF_SIZE); public UTF8Coder() { decoder.onMalformedInput(CodingErrorAction.REPLACE); decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); } public byte[] encode(CharSequence cs) { ByteBuffer buffer; if (cs.length() > CHAR_THRESHOLD) { buffer = UTF8.encode(cs.toString()); } else { buffer = byteBuffer; CharBuffer cbuffer = charBuffer; buffer.clear(); cbuffer.clear(); cbuffer.put(cs.toString()); cbuffer.flip(); encoder.encode(cbuffer, buffer, true); buffer.flip(); } byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return bytes; } public String decode(byte[] bytes, int start, int length) { CharBuffer cbuffer; if (length > CHAR_THRESHOLD) { cbuffer = UTF8.decode(ByteBuffer.wrap(bytes, start, length)); } else { cbuffer = charBuffer; ByteBuffer buffer = byteBuffer; cbuffer.clear(); buffer.clear(); buffer.put(bytes, start, length); buffer.flip(); decoder.decode(buffer, cbuffer, true); cbuffer.flip(); } return cbuffer.toString(); } public String decode(byte[] bytes) { return decode(bytes, 0, bytes.length); } } /** UTF8Coder wrapped in a SoftReference to avoid possible ClassLoader leak. See JRUBY-6522 */ private static final ThreadLocal<SoftReference<UTF8Coder>> UTF8_CODER = new ThreadLocal<SoftReference<UTF8Coder>>(); private static UTF8Coder getUTF8Coder() { UTF8Coder coder; SoftReference<UTF8Coder> ref = UTF8_CODER.get(); if (ref == null || (coder = ref.get()) == null) { coder = new UTF8Coder(); ref = new SoftReference<UTF8Coder>(coder); UTF8_CODER.set(ref); } return coder; } @JRubyMethod(name = "list", meta = true) public static IRubyObject list(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; return RubyArray.newArrayNoCopy(runtime, runtime.getEncodingService().getEncodingList(), 0); } @JRubyMethod(name = "locale_charmap", meta = true) public static IRubyObject locale_charmap(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; EncodingService service = runtime.getEncodingService(); ByteList name = new ByteList(service.getLocaleEncoding().getName()); return RubyString.newUsAsciiStringNoCopy(runtime, name); } @SuppressWarnings("unchecked") @JRubyMethod(name = "name_list", meta = true) public static IRubyObject name_list(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; EncodingService service = runtime.getEncodingService(); RubyArray result = runtime.newArray(service.getEncodings().size() + service.getAliases().size()); HashEntryIterator i; i = service.getEncodings().entryIterator(); while (i.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) i.next()); result.append( RubyString.newUsAsciiStringShared(runtime, e.bytes, e.p, e.end - e.p).freeze(context)); } i = service.getAliases().entryIterator(); while (i.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) i.next()); result.append( RubyString.newUsAsciiStringShared(runtime, e.bytes, e.p, e.end - e.p).freeze(context)); } result.append(runtime.newString(EXTERNAL)); result.append(runtime.newString(LOCALE)); return result; } @SuppressWarnings("unchecked") @JRubyMethod(name = "aliases", meta = true) public static IRubyObject aliases(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; EncodingService service = runtime.getEncodingService(); IRubyObject list[] = service.getEncodingList(); HashEntryIterator i = service.getAliases().entryIterator(); RubyHash result = RubyHash.newHash(runtime); while (i.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) i.next()); IRubyObject alias = RubyString.newUsAsciiStringShared(runtime, e.bytes, e.p, e.end - e.p).freeze(context); IRubyObject name = RubyString.newUsAsciiStringShared(runtime, ((RubyEncoding) list[e.value.getIndex()]).name) .freeze(context); result.fastASet(alias, name); } result.fastASet( runtime.newString(EXTERNAL), runtime.newString(new ByteList(runtime.getDefaultExternalEncoding().getName()))); result.fastASet( runtime.newString(LOCALE), runtime.newString(new ByteList(service.getLocaleEncoding().getName()))); return result; } @JRubyMethod(name = "find", meta = true) public static IRubyObject find(ThreadContext context, IRubyObject recv, IRubyObject str) { Ruby runtime = context.runtime; // Wacky but true...return arg if it is an encoding looking for itself if (str instanceof RubyEncoding) return str; return runtime.getEncodingService().rubyEncodingFromObject(str); } @JRubyMethod(name = "_dump") public IRubyObject _dump(ThreadContext context, IRubyObject arg) { return to_s(context); } @JRubyMethod(name = "_load", meta = true) public static IRubyObject _load(ThreadContext context, IRubyObject recv, IRubyObject str) { return find(context, recv, str); } @JRubyMethod(name = "ascii_compatible?") public IRubyObject asciiCompatible_p(ThreadContext context) { return context.runtime.newBoolean(getEncoding().isAsciiCompatible()); } @JRubyMethod(name = {"to_s", "name"}) public IRubyObject to_s(ThreadContext context) { // TODO: rb_usascii_str_new2 return RubyString.newUsAsciiStringShared(context.runtime, name); } @JRubyMethod(name = "inspect") public IRubyObject inspect(ThreadContext context) { ByteList bytes = new ByteList(); bytes.append("#<Encoding:".getBytes()); bytes.append(name); if (isDummy) bytes.append(" (dummy)".getBytes()); bytes.append('>'); return RubyString.newUsAsciiStringNoCopy(context.runtime, bytes); } @SuppressWarnings("unchecked") @JRubyMethod(name = "names") public IRubyObject names(ThreadContext context) { Ruby runtime = context.runtime; EncodingService service = runtime.getEncodingService(); Entry entry = service.findEncodingOrAliasEntry(name); RubyArray result = runtime.newArray(); HashEntryIterator i; i = service.getEncodings().entryIterator(); while (i.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) i.next()); if (e.value == entry) { result.append( RubyString.newUsAsciiStringShared(runtime, e.bytes, e.p, e.end - e.p).freeze(context)); } } i = service.getAliases().entryIterator(); while (i.hasNext()) { CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry> e = ((CaseInsensitiveBytesHash.CaseInsensitiveBytesHashEntry<Entry>) i.next()); if (e.value == entry) { result.append( RubyString.newUsAsciiStringShared(runtime, e.bytes, e.p, e.end - e.p).freeze(context)); } } result.append(runtime.newString(EXTERNAL)); result.append(runtime.newString(LOCALE)); return result; } @JRubyMethod(name = "dummy?") public IRubyObject dummy_p(ThreadContext context) { return context.runtime.newBoolean(isDummy); } @JRubyMethod(name = "compatible?", meta = true) public static IRubyObject compatible_p( ThreadContext context, IRubyObject self, IRubyObject first, IRubyObject second) { Ruby runtime = context.runtime; Encoding enc = areCompatible(first, second); return enc == null ? runtime.getNil() : runtime.getEncodingService().getEncoding(enc); } @JRubyMethod(name = "default_external", meta = true, compat = RUBY1_9) public static IRubyObject getDefaultExternal(IRubyObject recv) { return recv.getRuntime().getEncodingService().getDefaultExternal(); } @JRubyMethod(name = "default_external=", meta = true, compat = RUBY1_9) public static IRubyObject setDefaultExternal(IRubyObject recv, IRubyObject encoding) { Ruby runtime = recv.getRuntime(); EncodingService service = runtime.getEncodingService(); if (encoding.isNil()) { throw recv.getRuntime().newArgumentError("default_external can not be nil"); } runtime.setDefaultExternalEncoding(service.getEncodingFromObject(encoding)); return encoding; } @JRubyMethod(name = "default_internal", meta = true, compat = RUBY1_9) public static IRubyObject getDefaultInternal(IRubyObject recv) { return recv.getRuntime().getEncodingService().getDefaultInternal(); } @JRubyMethod(name = "default_internal=", required = 1, meta = true, compat = RUBY1_9) public static IRubyObject setDefaultInternal(IRubyObject recv, IRubyObject encoding) { Ruby runtime = recv.getRuntime(); EncodingService service = runtime.getEncodingService(); if (encoding.isNil()) { recv.getRuntime().newArgumentError("default_internal can not be nil"); } recv.getRuntime().setDefaultInternalEncoding(service.getEncodingFromObject(encoding)); return encoding; } @Deprecated public static IRubyObject getDefaultExternal(Ruby runtime) { return runtime.getEncodingService().getDefaultExternal(); } @Deprecated public static IRubyObject getDefaultInternal(Ruby runtime) { return runtime.getEncodingService().getDefaultInternal(); } @Deprecated public static IRubyObject convertEncodingToRubyEncoding(Ruby runtime, Encoding defaultEncoding) { return runtime.getEncodingService().convertEncodingToRubyEncoding(defaultEncoding); } @Deprecated public static Encoding getEncodingFromObject(Ruby runtime, IRubyObject arg) { return runtime.getEncodingService().getEncodingFromObject(arg); } }