예제 #1
0
  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);
      }
    }
  }
예제 #2
0
 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);
 }
예제 #3
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);
 }
예제 #4
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;
  }
예제 #5
0
  /**
   * 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;
  }
예제 #6
0
 public Node toYamlNode(Representer representer) throws IOException {
   return representer.scalar(taguri(), ByteList.create(data.toString()), (char) 0);
 }
예제 #7
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;
  }
}
예제 #8
0
/** 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();
}
예제 #9
0
/**
 * 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;
  }
}
예제 #10
0
@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");
    }
  }
}
예제 #11
0
  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;
  }
예제 #12
0
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()));
  }
}
예제 #13
0
@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;
  }
}
예제 #14
0
@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);
  }
}